检查指针是否分配了内存


74

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

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

由于用户可能会忘记做内存分配,而只是将指针传递给我的函数,因此我想检查一下。所以有什么办法可以检查我的函数,以查看传递的指针是否确实分配了buf_siz的内存量。

EDIT1:似乎没有标准库可以检查它..但是有没有脏东西可以检查它.. ??

EDIT2:我确实知道我的函数将由优秀的C程序员使用...但是我想知道是否可以检查..如果我们愿意听..

结论:因此,无法检查某个指针是否在函数中分配了内存


3
我确实不这么认为,但是我没有足够的自信来发表答案。
易卜拉欣

除非您使用内存管理器或自行运行,否则无法进行检查。
Michael Foukarakis 09年

如果它是一个字符指针,我们可以执行strlen()或sizeof()并检查分配了多少内存(当然,如果字符串以NULL终止)。对于其他类型,我不确定是否有某种方法。
mk..14-2-18

我知道这是一个老问题,但是可以在不使用黑客的情况下跟踪分配的内存。下面的代码提供了一些摘要,可以帮助您入门。
c1moore

1
应该得出的结论是,即使有可能也不要检查。本文介绍了此问题。以Windows术语编写时,问题不是Windows特定的。
ikegami,

Answers:


32

除了某些特定于实现的黑客措施外,您无法检查。

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

您只需要假设您的客户知道如何使用C进行编程即可。我能想到的唯一解决方案是自己分配内存并返回它,但这并不是一个小小的改变。(这是一个较大的设计更改。)


指针可能不为null,但仍未分配buf_siz字节。我认为实际上没有任何方法可以检查询问者的要求。
易卜拉欣

1
好吧,那呢?由于这是C,因此可能使用的客户端会在无法分配内存时malloc返回一个NULL指针。那么...malloc我们信任吗?
雅各布

如果您要说的是,则由客户端确保在调用该函数之前malloc是否工作。
GManNickG

@jacob-我确实知道我们可以在malloc处进行检查...但是如果客户端忘记执行malloc则将导致分段错误..我想避免这种情况。
09年

7
是的 最后的结论是您的功能应该只做一件事和一件事。想象一下,如果每个函数都确保从参数访问的内存有效,那么开销会很大。只需让您的函数执行应做的事情即可。
GManNickG

10

下面的代码是我曾经用来检查某些指针是否试图访问非法内存的代码。该机制是诱导SIGSEGV。SEGV信号之前已重定向到私有函数,该函数使用longjmp返回程序。这是一种破解,但可以。

可以改进代码(使用“签名”而不是“信号”等),但这只是一个想法。我不确定它是否可以移植到其他Unix版本。注意,SIGSEGV信号不应在程序的其他地方使用。


3
@Sacoi = malloc(1);是有效的C代码,并且优于i = (int*) malloc(1);。也许您在想另一种语言。
chux-恢复莫妮卡

注意POSIX下,setjmp()longjmp()也许应该被替换成sigsetjmp()siglongjmp()。见stackoverflow.com/questions/20755260/...
安德鲁汉勒

恕我直言,不能保证无效的内存访问将导致SEGV-c = *(char *)(x);即使x未指向分配的区域,您也可以通过。SEGV仅在指针指向不可访问的内存段内(但该段的大小为几kB)时才触发,因此,如果在处分配4个字节10,则20尽管内存地址不在分配的区域内,但mem地址仍在同一段中作为address 10,因此尽管未分配,但您仍可以在20没有SEGV的情况下访问地址。
Michael Beer

这就是为什么您始终应该将未使用的指针设置为的NULL原因,因为如果尝试取消引用它,则可以保证此值导致SEGV。
Michael Beer

9

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

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


@Greg-对不起,我对WIN32函数没有太多的兴趣..如果可能的话,因为没有标准的C函数,工作良好的肮脏技巧也是可以的
09年

2
好吧,你没有指定你什么平台感兴趣的指定平台和编译器可以让你更具体的答案。
2009年


7

我总是将指针初始化为空值。因此,当我分配内存时,它将改变。当我检查是否已分配内存时,我会执行pointer != NULL。当我释放内存时,我还将指针设置为null。我想不出任何方法来判断是否分配了足够的内存。

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


@Yelonek ..我确实同意你的意见,但我真的很想知道是否有任何可能的检查方法……
09年

1
我也这样做,但是(尤其是在图书馆中)发生了s ***。
Sellorio

7

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

但是,这种技巧非常难以移植,如果您的代码依赖于此类内容,那么就该重新考虑代码的体系结构了。您可能做错了什么。


4

不,通常没有办法。

此外,如果您的接口只是“将指针传递到我将要放入东西的缓冲区”,则调用方可能会选择根本不分配内存,而是使用静态分配的固定大小的缓冲区或自动变量或其他东西。也许它是指向堆中较大对象一部分的指针。

如果您的接口专门说“传递一个指向已分配内存的指针(因为我要对其进行分配)”,那么您应该期望调用者会这样做。失败不是您可以可靠地检测到的东西。


虽然这通常是最好的答案,并且大多数情况下是正确的,但我会说:只要付出了足够的努力,您就可以实现自己的自定义加载程序来跟踪所有内存分配-或使用现有的工具,如valgrind;)
Michael Beer

3

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

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


是的...如果我编写了整个应用程序,我可以做到..但是为了使用一个函数检查事物可能会很复杂..?
09年

这有可能导致某人认为未初始化的指针在堆上。另外,如果有人碰巧将指针存储到堆栈的更下方(向上?),然后又弹出该堆栈以获取您的函数,则该堆栈也将被考虑。
凯文

根据已在堆或堆栈上分配指针来区分指针并没有真正的帮助-char copy[255] = {0}; snprintf(copy, sizeof(copy), "%n: %s\n", error_code, error_msg); copy[sizeof(copy) -1] = 0; write(log_fd, copy, strnlen(copy) + 1); copy[0] = 0; 如果snprintf执行像您建议的那样奇怪的检查,snprintf又会错误地认为copy是无效的指针呢?
Michael Beer

3

我知道这是一个老问题,但是在C语言中几乎一切皆有可能。这里已经有一些骇人听闻的解决方案,但是确定内存是否已正确分配的一种有效方法是使用oracle代替malloccallocrealloc,和free。这与测试框架(例如cmocka)检测内存问题(例如故障,无法释放内存等)的方式相同。您可以维护分配的内存地址列表,并在用户要使用您的功能时简单地检查该列表。我为自己的测试框架实现了非常相似的功能。一些示例代码:

您将有类似的功能callocrealloc以及free与前缀每个包装__wrap_。实malloc数可以通过使用__real_malloc(对于您所包裹的其他功能类似)。每当您要检查是否实际分配了内存时,只需遍历链接memory_ref列表并查找内存地址即可。如果找到它并且它足够大,则可以肯定地知道该内存地址不会使您的程序崩溃。否则,返回错误。在程序使用的头文件中,您将添加以下行:

我的需求非常简单,因此我实现了一个非常基本的实现,但是您可以想象如何扩展它以拥有更好的跟踪系统(例如,创建一个struct除了大小之外还可以跟踪内存位置的跟踪系统)。然后,您只需使用

缺点是用户必须使用上述指令编译其源代码。但是,这与我见过的最糟糕的情况相去甚远。分配和释放内存会有一些开销,但是在增加安全性时总会有一些开销。


2

我不知道从库调用中执行此操作的方法,但是在Linux上,您可以查看 /proc/<pid>/numa_maps。它将显示内存的所有部分,第三列将显示“堆”或“堆栈”。您可以查看原始指针值以查看其排列位置。

例:

因此,高于0x01167000但低于0x7f39904d2000的指针位于堆中。


1

您无法检查标准C中提供的任何内容。即使您的特定编译器提供了执行此功能的功能,也仍然不是一个好主意。这是为什么的示例:


@Mark-在代码中,您将str分配为大小为COUNT ..的数组,因此在“ YourFunc”中,我仍然可以在buf_size的大小内执行类似strcpy的操作。但是,如果str只是一个char指针,则尝试执行大小为buf_size的任何strcpy操作都将导致“分段错误”
encodingfreak

2
编码错误,这是非常错误的。如果'str'是指向您不允许访问的内存的char指针,则会发生分段错误。不会发生这种情况是因为'str'是一个char指针,而是因为您要求程序执行不允许执行的操作。
gnud

1

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

到目前为止,没有人提到史蒂夫·马奎尔(Steve Maguire)的“编写可靠代码”。尽管在某些方面受到了谴责,但该书还是有一些关于内存管理的章节,并讨论了如何谨慎地并完全控制程序中所有内存的分配,如何根据您的要求进行操作,并确定给定的指针是否是一个指向动态分配内存的有效指针。但是,如果您打算使用第三方库,则会发现其中很少有第三方库允许您将内存分配例程更改为自己的例程,这使这种分析变得非常复杂。


@Jonathan-第三方图书馆的意思-?? 我只是在使用标准库和ISO C99。但是我只会尝试您推荐的书。
09年

第三方库是您没有编写的任何东西,包括标准库。粗略地说,如果它在任何地方使用malloc(),您将很难用自己的内存分配器替换那些调用,这意味着将很难跟踪滥用情况。您可能需要寻找更复杂的内存跟踪工具-检查malloc,valgrind,Purify等的调试版本。(这是我一生的祸根-我们无法从外部使用大多数库,因为我的产品的工作具有令人难以置信的内存管理要求,而图书馆既不了解也不在乎。)
Jonathan Leffler 2009年

1

通常,lib用户负责输入检查和验证。您可能会在ASS库代码中看到ASSERT或某些内容,它们仅用于调试。这是编写C / C ++时的标准方法。许多程序员喜欢非常仔细地在他们的lib代码中进行这种检查和验证。真的是“坏”习惯。如IOP / IOD中所述,lib接口应该是契约,并明确lib将要做什么,不应该做什么,lib用户应该做什么以及不需要什么。


1

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

确保他使用您的函数分配内存,例如

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

如果struct_type的定义是

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

这样,除非他将temp-> string分配给一个值,否则它将保持为NULL。您可以检入使用此结构的函数(字符串是否为NULL)。

还有一件事,如果程序员非常糟糕,以至于他无法使用您的函数,而是直接访问未分配的内存,那么他就不应该使用您的库。只要确保您的文档中指定了所有内容即可。


1

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

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

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

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

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


0

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


@Chuck如果没有标准库函数来检查它是否还有其他出路..?
09年

0

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

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

作为有效性结构,您可以使用块的大小及其补码。这样,您不仅可以验证块(将两个值进行异或并与零进行比较),而且还可以获得有关块大小的信息。


您可能想检查一下您的第一句话:“初始化指针正是未初始化的指针。”
克里斯·卢兹

0

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

用法:

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

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

检查失败的指针PtrCheck();

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

PtrFree();


-1

计算机几乎从来没有“从来没有”。跨平台超出了预期。25年后,我参与了数百个预期跨平台的项目,但从未实现。

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

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

太简单。嘿,正确的C ++吗?否。正确重要吗?在25年中,我看到了更多关于正确性的估计。好吧,让我们这样说:如果您是黑客,则不是在进行真正的编程,您可能只是在修改已完成的内容。

那有多有趣?


1
最初的问题是关于C的,不是C ++的,没有提及也不暗示堆栈变量,也不是关于有趣/新颖/独特的东西。
Alexey Frunze'4

此外,malloc类似功能不一定执行将导致堆扩展的操作。C ++具有一种全新的分配内存的方式,每个人都知道使用C的预定义函数不是一个好主意。
Imobilis '16

只知道您传递了一个指向堆栈某处的指针是没有用的。您仍然必须解决OP的问题,即知道指向缓冲区的大小。
melpomene
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.