检查指针是否指向堆上已分配的内存

16

我想知道一个指针是否指向使用malloc/new分配的内存。我知道对于任意地址而言答案都是"不行",但我认为可以重写malloc/free并跟踪已分配的内存范围。

你知道提供这种特定工具的内存管理库吗?
你知道生产代码中有什么类似的东西吗?

Valgrind非常好,但它太过于仪器化(慢),正如Will所说,我们不想像这样使用Valgrind(让软件崩溃就足够了)。
Mudflap是一个非常好的解决方案,但专门针对GCC,不幸的是,检查并不简单地返回一个布尔值(请参见下面的我的回答)。
请注意,检查内存写入是否合法是一个安全问题。因此,寻求性能是有动机的。


+1,这是一个至关重要的问题,需要测试POD内存(即没有构造函数和析构函数)是否被正确分配和释放。我猜C++库在其堆管理机制中有答案,因为它需要跟踪已分配的内存块及其大小。但我不知道它是否公开了这些数据(如果没有,是否可能侵入获取数据)-我正在寻找答案。 - Serge Rogatch
你确切地需要它做什么?是想知道指针是否有效,还是想知道它是否是堆指针? - Paweł Bylica
10个回答

13

一个证明它可能无法有用地完成:

char * p1 = malloc(1);
free( p1 );
char * p2 = malloc(1);   // probably allocates same block as first malloc

现在p1和p2都指向堆上的同一块内存,但只有p2是有效的。


7
哲学上可能会让人失望,但如果p2最终指向与p1相同的地址,那么p1仍然是可修改内存的地址。对我来说这很好。 - log0
1
也许p2的地址与p1相同...这取决于你的malloc如何工作(在valgrind下运行几乎可以保证p2 != p1,这是有意为之的,以便您可以检测到释放后使用,但好的malloc可能会或可能不会这样做(其中一些维护特定大小的最近释放对象的FIFO队列,它们不会返回p1...))。 - Spudd86
1
进一步来说:如果通过一些任意的事件序列,旧指针p1所指向的地址最终位于新分配块的中间而不是精确匹配p2,那么p1仍然可以被认为指向堆的一部分,但单独使用p1在我能想到的任何合理方式下都没有用处。 - TheUndeadFish
1
(晚来的评论)在此代码之后,p1 的任何访问都是错误的。大多数分析工具都无法捕获此问题。我们的 CheckPointer 工具 捕获此代码执行后通过 p1 的任何间接访问。请参见本主题中我的答案。 - Ira Baxter
1
@log0 指针不是地址。在被释放后,指针p1的值变得不确定,符合标准。读取它将导致未定义的行为。 - 2501
显示剩余9条评论

10

目前没有标准方法来实现这个,但是各种内存分配调试工具可能有自己的实现方式。例如,如果你使用valgrind,你可以使用VALGRIND_CHECK_MEM_IS_ADDRESSABLE来检查它以及相关的事情。


1
@Ugo 可以说,与库链接不是标准的。但是撇开这个不谈,像 valgrind 这样的工具使用的库中的内容绝对是非标准的,这就是它们为什么是特定于平台的原因。 - anon
1
@matias,无论是C++还是C标准都没有真正描述这样的东西-但这不是这个问题的重点。 - anon
@matias,无论是C++还是C标准都没有真正描述广度优先搜索,这意味着它在标准上可能不被认可。 - log0
2
如果你使用valgrind,你不需要检查地址是否可寻址,只需让valgrind在出现错误时崩溃,然后修复它 :) - Will
1
@Neil/jalf/Chris,你们说得没错,链接并没有标准化,但你们仍然在使用标准的C++。同样的道理,平台特定的代码,在你的代码中分配视频帧缓冲区地址并不会使你的代码非标准化,只是使它变成了平台特定的。 - log0
显示剩余7条评论

5
如果您的应用程序性能不是真正的问题,您可以自行完成此操作:
定义MyMalloc(...)和MyFree(...),其中除了调用malloc/free之外,还要更新一对列表{address--malloc的结果,blockSize--请求的内存量}。然后,当您需要检查指针p时,您将寻找满足以下条件的一对:address <= p <= address + blockSize。
如果您想要实际使用该指针,其他条件可能/应该被检查,这只会告诉您地址是否正在使用中。

3
在 macOS/Darwin 上,是可以的。
将指针传递给 malloc_zone_from_ptr(…) 函数;如果它返回 NULL,那么该指针并未使用任何变体的 malloc/calloc/等分配。这与释放非 malloc 指针时出现的错误信息和 SIGABRT 时使用的相同机制。请参见 Darwin's malloc.c
以下是一些测试代码:
#include <malloc/malloc.h>int *mallocAllocated = (int *)malloc(16);
int *newAllocated = new int[16];
int stackAllocated[16];

printf("mallocAllocated allocated with malloc: %s\n",
    (malloc_zone_from_ptr(mallocAllocated) != NULL) ? "yes" : "no");
printf("newAllocated allocated with malloc: %s\n",
    (malloc_zone_from_ptr(newAllocated) != NULL) ? "yes" : "no");
printf("stackAllocated allocated with malloc: %s\n",
    (malloc_zone_from_ptr(stackAllocated) != NULL) ? "yes" : "no");

free(mallocAllocated);
free(newAllocated);
free(stackAllocated); // aborts with SIGABRT here

输出:

mallocAllocated allocated with malloc: yes
newAllocated allocated with malloc: yes
stackAllocated allocated with malloc: no

提示:这是否适用于所有Darwin平台和构建版本以及所有类型的内存分配?我不清楚。我所知道的只有源代码man malloc_zone_from_ptr

为什么您要在macOS /«Apple»OS / Darwin系统上特别执行此操作?嗯,如果您在堆栈分配Objective-C对象并且想确保不调用[super dealloc]到NSObject的实现中,那将是一个比较特殊的用例。


2
Mudflap(适用于gcc)似乎非常不错。你需要使用它来编译你的软件,但它将检查任何错误指针访问(堆栈/静态)。它被设计用于生产代码,其减速估计在x1.5到x5之间。你也可以禁用读取访问的检查以提高速度。
用户检查可以使用。
void __mf_check (void *ptr, __mf_size_t sz, int type, const char *location)

调用此函数会导致:根据环境参数,不产生任何结果、分叉到 gdb、段错误或中止。

1
请查看我们的CheckPointer工具,它将验证每个指针访问的有效性。虽然速度不是特别快,但它会捕捉到即使Valgrind无法捕捉到的错误(例如指向已释放的堆栈帧等)。 另一个回答此问题的链接展示了一种情况,即仅对指针有效性进行纯内存范围检查将无法检测到问题。他有点正确,因为如果仅有内存范围地址,则无法可靠地检查重新分配的存储块是否被误用。这被称为时间错误。通过将分配事件与内存块以及范围相关联,您可以检测到这种错误。Checkpointer就是这样做的,并且可以检测到该错误。

1
你可以使用LD_PRELOAD,并在自己的函数中包装malloc。

0

内存分配具有(虚拟)地址和长度。

指针仅包含地址。

如果您单独跟踪长度,则可以检查其是否包含,例如:

int check_contained(const char* src,size_t srclen,const char* sub,size_t sublen) {
   return (sub >= src) && (sub+sublen < src+srclen);
}

Symbian有一个AllocLen函数,但没有POSIX或win32的等效函数。

请问你能否更新AllocLen的链接?它好像挂了 :/ - Mateusz Piotrowski
2
@MateuszPiotrowski Symbian已经死了,现在似乎文档也是如此。 :( - Will

0

我做过类似的事情,但是不记得具体的编码方式了,也没有代码在手。

但基本思路是重载一个基类的newdelete。在new中设置一个静态标志(例如bool inDynamicAlloc=true)。在基类的构造函数中会检查这个标志。 如果它为真,则对象在堆上分配,在栈上则相反。

构造函数之后会重置该标志。

希望这可以帮到你。


0

您可以使用与保守式垃圾收集器相同的技术来确定指针对象是否指向堆内存。实际上,您可能可以从bdwgc本身中抄袭源代码。这将是一个非微不足道的任务,但这是您可以控制和根据需要移植的内容。(实际上已经完成了大部分移植工作。)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接