检查指针是不是已分配内存
Posted
技术标签:
【中文标题】检查指针是不是已分配内存【英文标题】:Checking if a pointer is allocated memory or not检查指针是否已分配内存 【发布时间】:2010-12-07 06:32:18 【问题描述】:我们能否检查传递给函数的指针是否在 C 中分配了内存?
我用 C 语言编写了自己的函数,它接受字符指针 - buf [指向缓冲区的指针] 和大小 - buf_siz [缓冲区大小]。实际上,在调用此函数之前,用户必须创建一个缓冲区并为其分配 buf_siz 的内存。
由于用户可能会忘记进行内存分配而只是将指针传递给我的函数,因此我想检查一下。那么有什么方法可以检查我的函数以查看传递的指针是否真的分配了 buf_siz 内存量.. ??
EDIT1:似乎没有标准库可以检查它..但是有没有什么肮脏的黑客可以检查它.. ??
EDIT2: 我知道我的函数会被优秀的 C 程序员使用……但我想知道我们是否可以检查……如果我们可以我想听听..
结论:因此无法在函数内检查特定指针是否分配了内存
【问题讨论】:
我真的不这么认为,但我没有足够的信心发布答案。 没有办法检查,除非你使用内存管理器或自己滚动。 如果它是一个字符指针,我们可以执行 strlen() 或 sizeof() 并检查分配了多少内存(当然,如果字符串以 NULL 结尾)。对于其他类型,我不确定有没有办法。! 我知道这是一个老问题,但可以在不使用 hack 的情况下跟踪分配的内存。我下面的代码提供了一些 sn-ps 来帮助您入门。 应该得出的结论是,即使有可能,您也不应该检查。 This article 解释了这个问题。虽然是用 Windows 术语编写的,但问题不是特定于 Windows 的。 【参考方案1】:除了一些特定于实现的黑客之外,您无法检查。
指针除了指向的位置之外没有其他信息。你能做的最好的就是说“我知道这个特定的编译器版本是如何分配内存的,所以我将取消引用内存,将指针移回 4 个字节,检查大小,确保它匹配......”等等。您不能以标准方式执行此操作,因为内存分配是实现定义的。更不用说他们可能根本没有动态分配它。
您只需要假设您的客户知道如何用 C 进行编程。我能想到的唯一解决方案是自己分配内存并返回它,但这并不是一个小改变。 (这是一个更大的设计变化。)
【讨论】:
指针可能不为空,但仍未分配 buf_siz 字节。我认为没有任何方法可以检查提问者想要什么。 好的,这个怎么样?由于这是 C,客户端可能使用了malloc
,如果无法分配内存,它会返回一个 NULL
指针。所以......我们信任malloc
?
在调用函数之前,由客户端确保 malloc 工作正常,如果这就是你的意思的话。
@jacob - 我知道我们可以在 malloc 处检查...但是如果客户端忘记执行 malloc 则会导致分段错误.. 我想避免它。
是的。最终结论是你的函数应该只做一件事和一件事。想象一下,如果每个函数都确保它从参数访问的内存是有效的,那么开销会很大。让你的函数做它应该做的事情。【参考方案2】:
您无法检查标准 C 中可用的任何内容。即使您的特定编译器提供了这样做的函数,这仍然是一个坏主意。下面是一个例子:
int YourFunc(char * buf, int buf_size);
char str[COUNT];
result = YourFunc(str, COUNT);
【讨论】:
@Mark - 在代码中,您将 str 分配为大小为 COUNT 的数组。因此在“YourFunc”中,我仍然可以在 buf_size 的大小内执行 strcpy 之类的操作。但是如果 str 只是一个 char 指针,那么尝试执行任何大小为 buf_size 的 strcpy 操作将导致“分段错误” 那是非常非常错误的,编码狂。如果 'str' 是指向您不允许访问的内存的 char 指针,则会发生分段错误。它不会发生,因为 'str' 是一个 char 指针,它发生是因为你要求程序做一些它不允许做的事情。【参考方案3】:不,你不能。您会注意到标准库或其他任何地方都没有函数这样做。那是因为没有标准的说法。调用代码只需要承担正确管理内存的责任。
【讨论】:
@Chuck 如果没有标准库函数来检查是否有其他出路.. ?【参考方案4】:不,一般来说没有办法做到这一点。
此外,如果您的界面只是“传递一个指向缓冲区的指针,我将在其中放置东西”,那么调用者可能会选择 not 来分配内存,而是使用固定大小的缓冲区那是静态分配的或自动变量之类的。或者它可能是指向堆上较大对象的一部分的指针。
如果您的界面明确表示“传递一个指向已分配内存的指针(因为我要释放它)”,那么您应该期望调用者会这样做。如果不这样做,您无法可靠地检测到。
【讨论】:
虽然这通常是最好的答案,而且大部分是正确的,但我想说:只要付出足够的努力,您可以实现自己的自定义加载器来跟踪所有内存分配 - 或使用现有工具,如 @987654321 @ ;)【参考方案5】:对于特定于平台的解决方案,您可能对 Win32 函数IsBadReadPtr
(以及其他类似的)感兴趣。此函数将能够(几乎)预测在读取特定内存块时是否会出现分段错误。
但是,在一般情况下,这不会保护您,因为操作系统对 C 运行时堆管理器一无所知,并且如果调用者传入的缓冲区没有如您所料,那么从操作系统的角度来看,堆块的其余部分将继续可读。
【讨论】:
@Greg - 很抱歉我对 WIN32 函数不太感兴趣.. 如果可能的话,一个运行良好的脏 hack 也可以,因为没有标准的 C 函数 好吧,你没有指定你是感兴趣的平台。指定平台和编译器可能会给你一个更具体的答案。 IsBadXxxPtr should really be called CrashProgramRandomly.【参考方案6】:我总是将指针初始化为空值。因此,当我分配内存时,它会改变。当我检查是否分配了内存时,我会做pointer != NULL
。当我释放内存时,我还将指针设置为空。我想不出任何方法来判断是否分配了足够的内存。
这并不能解决您的问题,但您必须相信,如果有人编写 C 程序,那么他的技能足以将其做好。
【讨论】:
@Yelonek .. 我同意你的观点,但我真的很想知道是否有任何可能检查 .... 我也是,但(尤其是在图书馆)s*** 发生了。【参考方案7】:正如其他人所说,没有标准的方法。
到目前为止,没有人提到史蒂夫·马奎尔的“Writing Solid Code”。尽管在某些quarters 中受到严厉批评,但这本书有关于内存管理主题的章节,并讨论了如何小心和完全控制程序中的所有内存分配,您可以按照您的要求进行操作并确定是否给您一个指针是指向动态分配内存的有效指针。但是,如果您打算使用第三方库,您会发现其中很少有允许您将内存分配例程更改为您自己的,这使此类分析变得非常复杂。
【讨论】:
@Jonathan - 你所说的第三方库是什么意思 - ??我只是使用标准库和 ISO C99。但我会试试你推荐的书。 第三方库是您没有编写的任何东西,包括标准库。粗略地说,如果它在任何地方使用 malloc(),您将很难用自己的内存分配器替换这些调用,这意味着很难跟踪滥用情况。你可能不得不去寻找更复杂的内存跟踪东西——检查 malloc、valgrind、Purify 等的调试版本(这是我生命中的祸根——我们不能在没有努力的情况下从外部使用大多数库,因为我的产品工作有令人痛苦的内存管理要求,图书馆既不知道也不关心。)【参考方案8】:您可以尝试的一个技巧是检查您的指针是否指向堆栈分配的内存。 这通常对您没有帮助,因为分配的缓冲区可能太小或者指针指向某个全局内存部分(.bss、.const、...)。
要执行此 hack,首先将第一个变量的地址存储在 main() 中。稍后,您可以将此地址与特定例程中的局部变量的地址进行比较。 两个地址之间的所有地址都位于堆栈上。
【讨论】:
是的......如果我编写整个应用程序,我可以做到......但是为了使用函数来检查事情可能很复杂......? 这有可能导致有人认为未初始化的指针在堆上。此外,如果有人碰巧存储了指向堆栈下方(上方?)某个位置的指针,该指针后来被弹出以获取您的函数,它也将被视为在堆上。 区分在堆或堆栈上分配的指针在这里并没有真正帮助 - 如果snprintf
会像你建议的那样执行奇怪的检查,snprintf
会错误地考虑copy
是一个无效的指针...【参考方案9】:
一般来说,lib 用户负责输入检查和验证。您可能会在 lib 代码中看到 ASSERT 或其他内容,它们仅用于调试目的。这是编写 C/C++ 时的标准方式。而很多编码人员喜欢在他们的 lib 代码中非常仔细地进行此类检查和验证。真是“坏”的习惯。正如 IOP/IOD 中所述,lib 接口应该是合同,并明确 lib 将做什么和不做什么,以及 lib 用户应该做什么和不需要什么。
【讨论】:
【参考方案10】:一个未初始化的指针就是这样 - 未初始化的。它可能指向任何东西,也可能只是一个无效地址(即未映射到物理或虚拟内存的地址)。
一个实际的解决方案是在指向的对象中具有有效性签名。创建一个 malloc() 包装器,它分配请求的块大小加上签名结构的大小,在块的开头创建一个签名结构,但返回指向签名后位置的指针。然后,您可以创建一个接受指针的验证函数,使用负偏移量来获取有效性结构并对其进行检查。您当然需要相应的 free() 包装器来通过覆盖有效性签名来使块无效,并从分配块的真正开始处执行释放。
作为有效性结构,您可以使用块的大小及其补码。这样,您不仅可以验证块(对两个值进行异或并与零比较),而且还可以获得有关块大小的信息。
【讨论】:
你可能想检查你的第一句话:“一个初始化的指针就是那个 - 未初始化的。”【参考方案11】:计算机中几乎从来没有“从不”。跨平台超出预期。 25 年后,我参与了数百个项目,所有项目都期待跨平台,但从未实现。
显然,堆栈上的变量将指向堆栈上的一个区域,该区域几乎是线性的。跨平台垃圾收集器通过标记堆栈的顶部或(底部)来工作,调用一个小函数来检查堆栈是向上还是向下增长,然后检查堆栈指针以了解堆栈有多大。这是你的范围。我不知道有哪台机器不以这种方式实现堆栈(向上或向下)。
您只需检查我们的对象或指针的地址是否位于堆栈的顶部和底部之间。这样你就可以知道它是否是一个堆栈变量。
太简单了。嘿,它是正确的 c++ 吗?不,正确重要吗? 25 年来,我看到了更多的正确估计。好吧,让我们这样说吧:如果你在做黑客,你并不是在做真正的编程,你可能只是在重复一些已经完成的事情。
这有多有趣?
【讨论】:
最初的问题是关于 C 而不是 C++,没有提及或暗示堆栈变量,也不是关于有趣/新/独特的东西。 此外,malloc
-alike 函数不一定会执行最终会导致堆扩展的操作。 C++ 有一种全新的不同的内存分配方式,每个人都知道使用 C 的预定义函数不是一个好主意。
只知道传递了一个指向堆栈某处的指针是没有用的。您仍然需要解决 OP 的问题,即知道指向的缓冲区有多大。【参考方案12】:
有一个简单的方法可以做到这一点。每当您创建指针时,请在其周围编写一个包装器。例如,如果您的程序员使用您的库来创建结构。
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-> 字符串分配给一个值,否则它将保持为 NULL。如果字符串是否为 NULL,您可以检查使用此结构的函数。
还有一件事,如果程序员太糟糕了,以至于他无法使用您的函数,而是直接访问未分配的内存,那么他不应该使用您的库。只需确保您的文档说明了所有内容。
【讨论】:
【参考方案13】:我曾经在我的 64 位 Solaris 上使用了一个肮脏的 hack。在 64 位模式下,堆从 0x1 0000 0000 开始。通过比较指针,我可以确定它是数据或代码段中的指针 p < (void*)0x100000000
、堆中的指针 p > (void*)0x100000000
还是内存映射区域中的指针 @ 987654323@(mmap 从可寻址区域的顶部返回地址)。
这允许我的程序在同一个映射中保存分配的和内存映射的指针,并让我的映射模块释放正确的指针。
但是这种技巧是高度不可移植的,如果您的代码依赖于类似的东西,那么是时候重新考虑代码的架构了。你可能做错了什么。
【讨论】:
【参考方案14】:下面的代码是我曾经用来检查某个指针是否试图访问非法内存的代码。该机制是诱导 SIGSEGV。 SEGV 信号之前被重定向到一个私有函数,该函数使用 longjmp 返回程序。这是一种 hack,但它确实有效。
代码可以改进(使用'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);
【讨论】:
@Sacoi = malloc(1);
是有效的 C 代码,优于 i = (int*) malloc(1);
。也许您正在考虑另一种语言。
注意在 POSIX 下,setjmp()
和 longjmp()
可能应该替换为 sigsetjmp()
和 siglongjmp()
。见***.com/questions/20755260/…
恕我直言,无法保证无效的内存访问会导致 SEGV - 您的 c = *(char *)(x);
可能会顺利通过,即使 x
没有指向分配的区域。 SEGV
仅在指针指向不可访问的内存段内时触发,但段的大小为几 kB,因此如果您在10
分配 4 个字节,则更改为内存地址 20
,尽管在外部分配的区域,仍然与地址10
在同一段中,因此虽然没有分配,但您仍然可以在没有 SEGV 的情况下访问地址20
。
这就是为什么你总是应该将未使用的指针设置为NULL
,因为如果你尝试取消引用它,这个值是保证会导致SEGV......不能保证对于任何其他内存地址。
@Michael Beer:“不能保证无效的内存访问会导致 SEGV” - 正确,但检查仍然有效。如果没有 SEGV 则可以访问内存。【参考方案15】:
我不知道从库调用中执行此操作的方法,但在 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 的指针位于堆中。
【讨论】:
【参考方案16】:我知道这是一个老问题,但在 C 语言中几乎一切皆有可能。这里已经有一些骇人听闻的解决方案,但确定内存是否已正确分配的有效方法是使用 oracle 代替malloc
、calloc
、realloc
和 free
。这与测试框架(例如 cmocka)可以检测内存问题(段错误、未释放内存等)的方式相同。您可以维护分配的内存地址列表,并在用户想要使用您的功能时简单地检查此列表。我为自己的测试框架实现了一些非常相似的东西。一些示例代码:
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;
calloc
、realloc
和 free
具有类似的功能,每个包装器都以 __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
缺点是用户必须使用上述指令编译他们的源代码;然而,这远非我所见过的最糟糕的。分配和释放内存有一些开销,但在添加安全性时总会有一些开销。
【讨论】:
【参考方案17】:指针跟踪器,跟踪并检查指针的有效性
用法:
创建内存 int * ptr = malloc(sizeof(int) * 10);
将指针地址添加到tracker 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;
【讨论】:
【参考方案18】:嗯,我不知道是否有人没有把它放在这里,或者它是否会在你的程序中出现。我在大学项目中遇到过类似的事情。
我很简单地解决了它 - 在 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 语言中你不必小心。
【讨论】:
【参考方案19】:我不确定 msync 有多快,但这是一个仅限 linux 的解决方案:
// Returns 1 if the ponter is mapped
int pointer_valid (void *p)
size_t pg_size = sysconf (_SC_PAGESIZE);
void *pg_start = (void *) ((((size_t)p) / pg_size) * pg_size);
return msync (pg_start, pg_size, MS_ASYNC) == 0;
【讨论】:
以上是关于检查指针是不是已分配内存的主要内容,如果未能解决你的问题,请参考以下文章