检查一个指针是否被分配了内存

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

我有自己的C函数,它接受一个字符指针 – buf [指向缓冲区的指针]和size – buf_siz [buffer size]。 实际上,在调用这个函数之前,用户必须创build一个缓冲区并为其分配buf_siz的内存。

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

编辑1:似乎没有标准库检查它..但有没有任何肮脏的黑客检查它..?

编辑2:我知道我的function将被一个好的C程序员使用…但我想知道我们是否可以检查或不..如果我们可以,我想听听它..

结论:所以不可能检查一个特定的指针是否在内存中分配或者不在一个函数中

你不能检查,除了一些实现特定的黑客。

指针除了指向的地方外,没有任何信息。 你可以做的最好的是说“我知道这个特定的编译器版本是如何分配内存的,所以我将取消引用内存,将指针移回4字节,检查大小,确保它匹配……”等等。 你不能以标准的方式来做,因为内存分配是实现定义的。 更何况他们可能没有dynamic分配它。

你只需要假设你的客户知道如何用C编程。我能想到的唯一的解决scheme就是自己分配内存并返回它,但这不是一个小小的改变。 (这是一个更大的devise更改。)

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

但是,在一般情况下,这并不能保护您,因为操作系统对C运行时堆pipe理器一无所知,而且如果调用者传入的缓冲区不像您期望的那么大,那么剩下的堆块将继续从操作系统的angular度来看是可读的。

下面的代码是我曾经用来检查一些指针是否尝试访问非法内存的东西。 其机制是诱导一个SIGSEGV。 之前SEGV信号被redirect到一个私有函数,它使用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); } 

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

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

我总是初始化指针为空值。 因此,当我分配内存时,它会改变。 当我检查内存是否已分配我做pointer != NULL 。 当我释放内存时,我也将指针设置为null。 我想不出有没有足够的内存分配。

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

不,一般来说,没有办法做到这一点。

而且,如果你的接口只是“传递一个指向缓冲区的指针,那么调用者可能会select分配内存,而是使用静态分配的一个固定大小的缓冲区或一个自动variables或其他东西。 或者,也许它是指向堆中较大对象的一部分的指针。

如果你的接口专门说“传递一个指向已分配内存的指针(因为我要释放它)”,那么你应该期望调用者会这样做。 不这样做是不可靠的检测。

你可以尝试一个黑客检查你的指针是否指向堆栈分配的内存。 这通常不会对您有所帮助,因为分配的缓冲区可能很小,或者指针指向某个全局内存段(.bss,.const,…)。

为了执行这个hack,你首先在main()中存储第一个variables的地址。 稍后,您可以将此地址与特定例程中局部variables的地址进行比较。 两个地址之间的所有地址都位于堆栈上。

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

 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... 

我的需求相当简单,所以我实现了一个非常基本的实现,但是您可以想象如何将这个扩展到更好的跟踪系统(例如创build一个除了大小之外还可以跟踪内存位置的struct )。 然后你简单地编译代码

 gcc src_files -o dest_file -Wl,-wrap,malloc -Wl,-wrap,calloc -Wl,-wrap,realloc -Wl,-wrap,free 

缺点是用户必须用上面的指令编译他们的源代码。 然而,这远远不如我所见。 分配和释放内存有一些开销,但是在添加安全性时总会有一些开销。

你不能用标准C中的任何东西进行检查。即使你的特定编译器提供了这样做的function,它仍然是一个坏主意。 以下是一个例子:

 int YourFunc(char * buf, int buf_size); char str[COUNT]; result = YourFunc(str, COUNT); 

正如其他人所说,没有一个标准的方法来做到这一点。

到目前为止,还没有人提到史蒂夫·马奎尔(Steve Maguire)提出的“ 写固体代码 ”( Solid Code )。 尽pipe在某些方面受到严厉批评,本书还有关于内存pipe理主题的章节,并讨论了如何谨慎和完全控制程序中的所有内存分配,您可以根据您的要求来确定指针是否为指向dynamic分配内存的有效指针。 但是,如果您计划使用第三方库,您会发现其中很less有人允许您将内存分配例程更改为您自己的,这使分析非常复杂。

我不知道从库调用的方式,但在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的指针位于堆中。

你可以通过调用malloc/malloc.h malloc_size(my_ptr)它返回malloc为你的指针分配的大小,如果指针没有被分配,则返回0。 请记住,malloc重新调整分配的块的大小,以确保可以从该指针取消引用最具限制性的typesvariables并alignment内存。 所以,如果你调用malloc(1)(以及malloc(0)),malloc实际上返回16字节(在大多数机器上),因为限制最多的types的大小为16字节

不,你不能。 您会注意到标准库或其他任何地方都没有这样的function。 那是因为没有标准的方法来说明。 调用代码只是接受正确pipe理内存的责任。

一般lib用户负责input检查和validation。 您可能会在lib代码中看到ASSERT或其他东西,它们仅用于debugging通用。 这是编写C / C ++时的标准方法。 而很多编码人员都非常仔细地在他们的lib代码中进行这样的检查和确认。 真的是“坏”的习惯。 正如在IOP / IOD中所述,lib接口应该是契约,并且明确了lib将会做什么和不会做什么,以及lib用户应该做什么以及什么不是必须的。

一个未初始化的指针正是这个 – 未初始化的。 它可能指向任何东西,或者只是一个无效的地址(即没有映射到物理或虚拟内存的地址)。

一个实际的解决scheme是在指向的对象中有一个有效性签名。 创build一个malloc()包装器,用于分配请求的块大小和签名结构的大小,在块的起始位置创build一个签名结构,但将指针返回到签名后的位置。 然后,您可以创build一个validation函数,使用指针,使用负偏移量来获取有效性结构并进行检查。 你当然需要一个相应的free()包装器,通过覆盖有效性签名来使块失效,并从分配块的真正开始执行空闲。

作为一个有效的结构,你可以使用块的大小和补码。 这样,您不仅可以validation块(XOR两个值并将其比较为零),还可以获得有关块大小的信息。

电脑中几乎没有“永不”。 跨平台远远超过预期。 在25年的时间里,我已经从事了数百个项目,都在期待跨平台,而且从未实现过。

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

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

太简单。 嘿,这是正确的C + +? 不是,对吗? 25年来,我看到了更多的正确估计。 好吧,让我们这样说吧:如果你是黑客攻击,你并没有真正的编程,你可能只是在调整已经完成的任务。

那有多有趣?

有一个简单的方法来做到这一点。 每当你创build一个指针,写一个包装它。 例如,如果你的程序员使用你的库来创build一个结构。

 struct struct_type struct_var; 

确保他使用你的函数分配内存

 struct struct_type struct_var = init_struct_type() 

如果这个struct_var包含dynamic分配的内存,

如果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-> string分配给一个值,否则它将保持为NULL。 如果string为NULL,则可以检入使用此结构的函数。

还有一件事,如果程序员如此糟糕,他不能使用你的函数,而是直接访问未分配的内存,他不配使用你的库。 只要确保你的文档指定了一切。

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

用法:

创build内存int * ptr = malloc(sizeof(int)* 10);

将指针地址添加到跟踪器Ptr(&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; }