检查指针是否已分配内存

时间:2009-10-16 05:46:42

标签: c pointers

我们可以检查传递给函数的指针是否在C中分配了内存?

我在C中使用了自己的函数,它接受了一个字符指针 - buf [指向缓冲区的指针]和大小 - buf_siz [缓冲区大小]。实际上在调用此函数之前,用户必须创建一个缓冲区并为其分配buf_siz的内存。

由于用户可能忘记进行内存分配并只是将指针传递给我的函数,我想检查一下。那么有什么方法可以检查我的函数,看看传递的指针是否真的分配了buf_siz的内存量。??

EDIT1: 似乎没有标准库来检查它..但是有任何脏黑客来检查它吗?? ??

EDIT2: 我知道我的功能将由优秀的C程序员使用...但我想知道我们是否可以检查..如果我们可以,我想听听它..

结论:因此无法检查特定指针是否在函数内分配了内存

19 个答案:

答案 0 :(得分:29)

你无法检查,除了一些特定于实现的黑客攻击。

指针除了指出的地方外没有任何信息。您可以做的最好的事情是“我知道这个特定的编译器版本如何分配内存,所以我将取消引用内存,将指针移回4个字节,检查大小,确保它匹配......”等等。您无法以标准方式执行此操作,因为内存分配是实现定义的。更不用说他们可能根本没有动态分配它。

你必须假设你的客户知道如何在C中编程。我能想到的唯一解决方案是自己分配内存并返回它,但这不是一个小小的改变。 (这是一个更大的设计变化。)

答案 1 :(得分:8)

对于特定于平台的解决方案,您可能对Win32函数IsBadReadPtr(以及其他类似函数)感兴趣。此函数将能够(几乎)预测从特定内存块读取时是否会出现分段错误。

但是,在一般情况下,保护您,因为操作系统对C运行时堆管理器一无所知,并且如果调用者传入的缓冲区不是那么大您希望,从操作系统的角度来看,堆块的其余部分将继续可读。

答案 2 :(得分:8)

以下代码是我用过一次检查某些指针是否试图访问非法内存的代码。该机制是诱导SIGSEGV。 SEGV信号更早地被重定向到私有函数,它使用longjmp返回程序。它有点像黑客但它有效。

代码可以改进(使用'sigaction'而不是'signal'等),但它只是给出一个想法。它也可以移植到其他Unix版本,对于Windows我不确定。请注意,SIGSEGV信号不应在程序中的其他位置使用。

#include <stdio.h>
#include <stdlib.h>
#include <setjmp.h>
#include <signal.h>

jmp_buf jump;

void segv (int sig)
{
  longjmp (jump, 1); 
}

int memcheck (void *x) 
{
  volatile char c;
  int illegal = 0;

  signal (SIGSEGV, segv);

  if (!setjmp (jump))
    c = *(char *) (x);
  else
    illegal = 1;

  signal (SIGSEGV, SIG_DFL);

  return (illegal);
}

int main (int argc, char *argv[])
{
  int *i, *j; 

  i = malloc (1);

  if (memcheck (i))
    printf ("i points to illegal memory\n");
  if (memcheck (j))
    printf ("j points to illegal memory\n");

  free (i);

  return (0);
}

答案 3 :(得分:6)

我曾经在我的64位Solaris上使用过脏黑客。在64位模式下,堆从0x1 0000 0000开始。通过比较指针,我可以确定它是数据或代码段p < (void*)0x100000000中的指针,堆p > (void*)0x100000000中的指针还是指针中的指针。内存映射区域(intptr_t)p < 0(mmap返回可寻址区域顶部的地址)。 这允许我的程序在同一个映射中保存已分配和内存映射的指针,并让我的map模块释放正确的指针。

但是这种技巧非常难以移植,如果您的代码依赖于类似的东西,那么现在是时候重新思考代码的体系结构了。你可能做错了什么。

答案 4 :(得分:4)

我总是将指针初始化为null值。因此,当我分配内存时,它会改变。当我检查内存是否已分配时,我会pointer != NULL。当我释放内存时,我也将指针设置为null。我想不出有什么方法可以判断是否分配了足够的内存。

这并不能解决您的问题,但您必须相信,如果有人编写C程序,那么他就足够熟练地做到了。

答案 5 :(得分:3)

我知道这是一个老问题,但在C中几乎任何事情都是可能的。这里有一些hackish解决方案,但确定内存是否已正确分配的有效方法是使用oracle代替malloccallocreallocfree。这与测试框架(例如cmocka)可以检测内存问题(seg错误,不释放内存等)的方式相同。您可以维护分配时分配的内存地址列表,并在用户想要使用您的功能时查看此列表。我为自己的测试框架实现了非常类似的东西。一些示例代码:

typedef struct memory_ref {
    void       *ptr;
    int        bytes;
    memory_ref *next;
}

memory_ref *HEAD = NULL;

void *__wrap_malloc(size_t bytes) {
    if(HEAD == NULL) {
        HEAD = __real_malloc(sizeof(memory_ref));
    }

    void *tmpPtr = __real_malloc(bytes);

    memory_ref *previousRef = HEAD;
    memory_ref *currentRef = HEAD->next;
    while(current != NULL) {
        previousRef = currentRef;
        currentRef = currentRef->next;
    }

    memory_ref *newRef = (memory_ref *)__real_malloc(sizeof(memory_ref));
    *newRef = (memory_ref){
        .ptr   = tmpPtr,
        .bytes = bytes,
        .next  = NULL
    };

    previousRef->next = newRef;

    return tmpPtr;
}

您将拥有callocreallocfree的类似功能,每个包装器都以__wrap_为前缀。真正的malloc可通过使用__real_malloc获得(类似于您正在包装的其他功能)。每当您想要检查内存是否实际分配时,只需遍历链接的memory_ref列表并查找内存地址。如果你发现它并且它足够大,你肯定知道内存地址不会使你的程序崩溃;否则,返回错误。在程序使用的头文件中,您可以添加以下行:

extern void *__real_malloc  (size_t);
extern void *__wrap_malloc  (size_t);
extern void *__real_realloc (size_t);
extern void *__wrap_realloc (size_t);
// Declare all the other functions that will be wrapped...

我的需求相当简单,所以我实现了一个非常基本的实现,但你可以想象如何扩展它以获得更好的跟踪系统(例如创建一个跟踪内存位置的struct除了尺寸)。然后你只需用

编译代码
gcc src_files -o dest_file -Wl,-wrap,malloc -Wl,-wrap,calloc -Wl,-wrap,realloc -Wl,-wrap,free

缺点是用户必须使用上述指令编译其源代码;然而,它远没有我见过的更糟糕。分配和释放内存有一些开销,但在添加安全性时总会有一些开销。

答案 6 :(得分:2)

不,一般情况下无法做到这一点。

此外,如果您的界面只是“将指针传递给我将放置东西的缓冲区”,那么调用者可以选择 not 来分配内存,而是使用固定大小的缓冲区这是静态分配的或自动变量或其他东西。或者它可能是指向堆上较大对象的一部分的指针。

如果你的界面专门说“传递指向已分配内存的指针(因为我要解除分配它)”,那么你应该期望调用者会这样做。如果不这样做,你就无法可靠地发现。

答案 7 :(得分:2)

您可以尝试的一个方法是检查您的指针是否指向堆栈分配的内存。 这通常对您没有帮助,因为分配的缓冲区可能很小或指针指向某个全局内存部分(.bss,.const,...)。

要执行此hack,首先将第一个变量的地址存储在main()中。稍后,您可以将此地址与特定例程中的本地变量的地址进行比较。 两个地址之间的所有地址都位于堆栈中。

答案 8 :(得分:1)

您无法检查标准C中的任何可用内容。即使您的特定编译器提供了这样做的功能,它仍然是一个坏主意。以下是原因的一个例子:

int YourFunc(char * buf, int buf_size);

char str[COUNT];
result = YourFunc(str, COUNT);

答案 9 :(得分:1)

正如其他人所说,没有一种标准的方法可以做到。

到目前为止,没有人提到史蒂夫马奎尔的'Writing Solid Code'。虽然在一些quarters中受到严厉批评,但本书上有关于内存管理主题的章节,并讨论了如何谨慎和完全控制程序中的所有内存分配,你可以按照你的要求来确定指针是否为你给出的是一个动态分配内存的有效指针。但是,如果您计划使用第三方库,您会发现其中很少一些允许您将内存分配例程更改为您自己的,这使这种分析变得非常复杂。

答案 10 :(得分:1)

我不知道从库调用中这样做的方法,但在Linux上,你可以查看/proc/<pid>/numa_maps。它将显示内存的所有部分,第三列将显示“堆”或“堆栈”。您可以查看原始指针值以查看它的排列方式。

示例:

00400000 prefer:0 file=/usr/bin/bash mapped=163 mapmax=9 N0=3 N1=160
006dc000 prefer:0 file=/usr/bin/bash anon=1 dirty=1 N0=1
006dd000 prefer:0 file=/usr/bin/bash anon=9 dirty=9 N0=3 N1=6
006e6000 prefer:0 anon=6 dirty=6 N0=2 N1=4
01167000 prefer:0 heap anon=122 dirty=122 N0=25 N1=97
7f39904d2000 prefer:0 anon=1 dirty=1 N0=1
7f39904d3000 prefer:0 file=/usr/lib64/ld-2.17.so anon=1 dirty=1 N0=1
7f39904d4000 prefer:0 file=/usr/lib64/ld-2.17.so anon=1 dirty=1 N1=1
7f39904d5000 prefer:0 anon=1 dirty=1 N0=1
7fffc2d6a000 prefer:0 stack anon=6 dirty=6 N0=3 N1=3
7fffc2dfe000 prefer:0

所以高于0x01167000但低于0x7f39904d2000的指针位于堆中。

答案 11 :(得分:0)

未初始化的指针就是 - 未初始化。它可能指向任何地方或仅仅是无效地址(即未映射到物理或虚拟内存的地址)。

实际的解决方案是在指向的对象中使用有效性签名。创建一个malloc()包装器,它分配请求的块大小加上签名结构的大小,在块的开头创建一个签名结构,但返回签名后指向该位置的指针。然后,您可以创建一个获取指针的验证函数,使用负偏移量来获取有效性结构并进行检查。您当然需要一个相应的free()包装器来通过覆盖有效性签名使块无效,并从分配块的真正开始执行。

作为有效性结构,您可以使用块的大小及其一个补码。这样你不仅可以验证块(XOR这两个值并比较为零),但你也有关于块大小的信息。

答案 12 :(得分:0)

指针跟踪器,跟踪并检查指针的有效性

用法:

create memory int * ptr = malloc(sizeof(int)* 10);

将指针地址添加到跟踪器Ptr(&amp; ptr);

检查指针失败PtrCheck();

并释放代码末尾的所有跟踪器

PtrFree();

 #include <stdlib.h>
 #include <string.h>
 #include <stdio.h>
 #include <stdint.h>
 #include <stdbool.h>

struct my_ptr_t { void ** ptr; size_t mem; struct my_ptr_t *next, *previous; };

static struct my_ptr_t * ptr = NULL;

void Ptr(void * p){ 
                struct my_ptr_t * tmp = (struct my_ptr_t*) malloc(sizeof(struct my_ptr_t));
                printf("\t\tcreating Ptr tracker:");    
                if(ptr){ ptr->next = tmp; }
                tmp->previous = ptr;
                ptr = tmp;
                ptr->ptr = p;
                ptr->mem = **(size_t**) ptr->ptr;
                ptr->next = NULL;
                printf("%I64x\n", ptr); 
};
void PtrFree(void){
                    if(!ptr){ return; }
                    /* if ptr->previous == NULL */
                    if(!ptr->previous){ 
                                    if(*ptr->ptr){
                                                free(ptr->ptr);
                                                ptr->ptr = NULL;
                                    }
                                    free(ptr);
                                    ptr = NULL; 
                            return;                 
                    }

                    struct my_ptr_t * tmp = ptr;    
                    for(;tmp != NULL; tmp = tmp->previous ){
                                            if(*tmp->ptr){
                                                        if(**(size_t**)tmp->ptr == tmp->mem){
                                                                                                                                                    free(*tmp->ptr);
                                                                        *tmp->ptr = NULL;
                                                        }
                                            }
                                        free(tmp);
                    } 
            return; 
};

void PtrCheck(void){
                if(!ptr){ return; }
                if(!ptr->previous){
                        if(*(size_t*)ptr->ptr){
                                    if(*ptr->ptr){
                                                if(**(size_t**) ptr->ptr != ptr->mem){
                                                                printf("\tpointer %I64x points not to a valid memory address", ptr->mem);
                                                                printf(" did you freed the memory and not NULL'ed the pointer or used arthmetric's on pointer %I64x?\n", *ptr->ptr);
                                                                return; 
                                                        }   
                                    }
                                    return;
                                }
                        return;
                }
                struct my_ptr_t * tmp = ptr;
                for(;tmp->previous != NULL; tmp = tmp->previous){   
                                if(*(size_t*)tmp->ptr){         
                                                   if(*tmp->ptr){
                                                            if(**(size_t**) tmp->ptr != tmp->mem){
                                                                        printf("\tpointer %I64x points not to a valid memory address", tmp->mem);
                                                                        printf(" did you freed the memory and not NULL'ed the pointer or used arthmetric's on pointer %I64x?\n", *tmp->ptr);                            continue;
                                                            } 
                                                    }
                                                    continue;
                                }

                } 
            return;
       };

 int main(void){
        printf("\n\n\t *************** Test ******************** \n\n");
        size_t i = 0;
        printf("\t *************** create tracker ********************\n");
        int * ptr = malloc(sizeof(int) * 10);
        Ptr(&ptr);
        printf("\t *************** check tracker ********************\n");
        PtrCheck();
        printf("\t *************** free pointer ********************\n");
        free(ptr);
        printf("\t *************** check tracker ********************\n");
        PtrCheck();
        printf("\t *************** set pointer NULL *******************\n");
        ptr = NULL;
        printf("\t *************** check tracker ********************\n");
                PtrCheck();
        printf("\t *************** free tracker ********************\n");
        PtrFree();
        printf("\n\n\t *************** single check done *********** \n\n");
        printf("\n\n\t *************** start multiple test *********** \n");
        int * ptrs[10];
        printf("\t *************** create trackers ********************\n");
        for(; i < 10; i++){
                        ptrs[i] = malloc(sizeof(int) * 10 * i);
                        Ptr(&ptrs[i]);
                 }
        printf("\t *************** check trackers ********************\n");
        PtrCheck();
        printf("\t *************** free pointers but set not NULL *****\n");
        for(i--; i > 0; i-- ){ free(ptrs[i]); }
        printf("\t *************** check trackers ********************\n");
        PtrCheck(); 
        printf("\t *************** set pointers NULL *****************\n");
        for(i=0; i < 10; i++){ ptrs[i] = NULL; }
        printf("\t *************** check trackers ********************\n");
        PtrCheck(); 
        printf("\t *************** free trackers ********************\n");
        PtrFree();
        printf("\tdone");
    return 0;
 }

答案 13 :(得分:0)

你可以通过在malloc_size(my_ptr)中调用malloc/malloc.h来返回malloc为你的指针分配的大小,如果没有分配指针则返回0。请记住,malloc会调整已分配块的大小,以确保可以从该指针取消引用最具限制性的类型变量并对齐内存。因此,如果调用malloc(1)(以及malloc(0)),malloc实际上返回16个字节(在大多数机器上),因为限制性最强的类型的大小为16个字节

答案 14 :(得分:0)

有一种简单的方法可以做到这一点。每当你创建一个指针时,在它周围写一个包装器。例如,如果您的程序员使用您的库来创建结构。

struct struct_type struct_var;

确保使用您的功能分配内存,例如

struct struct_type struct_var = init_struct_type()

如果此struct_var包含动态分配的内存,例如

如果struct_type的定义是

typedef struct struct_type {
 char *string;
}struct_type;

然后在你的init_struct_type()函数中,执行此操作,

init_struct_type()
{ 
 struct struct_type *temp = (struct struct_type*)malloc(sizeof(struct_type));
 temp->string = NULL;
 return temp;
}

这样,除非他将temp-&gt;字符串分配给一个值,否则它将保持为NULL。如果字符串为NULL,则可以签入使用此结构的函数。

还有一件事,如果程序员非常糟糕,他没有使用你的功能,而是直接访问未分配的内存,他不配使用你的库。只需确保您的文档指定了所有内容。

答案 15 :(得分:0)

一般来说,lib用户负责输入检查和验证。您可能会在lib代码中看到ASSERT或其他内容,它们仅用于调试perpose。这是编写C / C ++时的标准方法。虽然很多程序员都非常谨慎地对他们的lib代码进行检查和验证。真的“不好”的习惯。正如IOP / IOD中所述,lib接口应该是契约,并明确lib将做什么和不做什么,以及lib用户应该做什么以及什么不应该做。

答案 16 :(得分:0)

不,你不能。您会注意到标准库或其他任何地方都没有这样的功能。那是因为没有标准的说法。调用代码只需承担正确管理内存的责任。

答案 17 :(得分:-1)

计算机中几乎从不“永不”。跨平台远远超出预期。 25年后,我参与了数百个项目,他们都期待跨平台,而且从未实现过。

显然,堆栈上的变量会指向堆栈上的一个区域,这几乎是线性的。跨平台垃圾收集器工作,通过标记堆栈的顶部或(底部),调用一个小函数来检查堆栈是向上还是向下增长,然后检查堆栈指针以了解堆栈的大小。这是你的范围。我不知道一台没有以这种方式实现堆栈的机器(无论是成长还是成长)。

您只需检查我们的对象或指针的地址是否位于堆栈的顶部和底部之间。这就是你如何知道它是否是一个堆栈变量。

太简单了。嘿,这是正确的c ++吗?没有。重要吗?在25年里,我看到了更多正确的估计方法。好吧,让我们这样说吧:如果你是黑客,你没有做真正的编程,你可能只是在谴责已经完成的事情。

那有多有趣?

答案 18 :(得分:-1)

嗯,我不知道是否有人没有把它放在这里,或者这是否有可能出现在您的程序中。我在大学项目中正为类似的事情而苦苦挣扎。

我非常简单地解决了它-在main()的初始化部分中,声明了LIST *ptr之后,我将其放在ptr=NULL中。像这样-

int main(int argc, char **argv) {

LIST *ptr;
ptr=NULL;

因此,当分配失败或完全没有分配指针时,它将为NULL。因此,您可以简单地使用if对其进行测试。

if (ptr==NULL) {
  "THE LIST DOESN'T EXIST" 
} else {
  "THE LIST MUST EXIST --> SO IT HAS BEEN ALLOCATED"
}

我不知道您的程序是如何编写的,但是您一定可以理解我要指出的内容。如果可以像这样检查您的分配,然后将参数传递给函数,则可能有一个简单的解决方案。

当然,您必须谨慎地完成分配和创建结构的函数,但是在C语言中您不必特别小心。