2

我有一些free()功能问题:

#include <stdio.h>
#include <stdlib.h>
int main() {
    char *a=malloc(24);
    char *b=malloc(24);
    free(a);
    free(a);
}

有一个“ * glibc detected * ./a.out: double free or corruption”。这并不奇怪,因为我用free()了两次。

但现在如果我这样做:

#include <stdio.h>
#include <stdlib.h>
int main() {
    char *a=malloc(24);
    char *b=malloc(24);
    free(a);
    free(b);
    free(a);
}

我的电脑没有错误,没有双重免费错误消息,但正如你所见,我的代码中有双重免费......

你知道出了什么问题吗?

4

4 回答 4

3

我无法回答为什么在您的第二个示例中碰巧没有检测到双重免费,除了猜测 libc 的主要目标是默认情况下是高性能的。因此,在默认情况下,库可能不会花太多精力来检查诸如 double-free 之类的东西。

但是,如果您在MALLOC_CHECK_环境变量设置为 1 的情况下运行程序,您确实会得到诊断stderr(将变量设置为 2 表示中止,3 表示中止,并将诊断和故障转储信息写入stderr):

$ gcc -o test test.c
$ ./test
$ MALLOC_CHECK_=1 ./test
*** glibc detected *** ./test: free(): invalid pointer: 0x00000000015e4010 ***

(注意环境变量名称后面的下划线)。

libc 的堆检查配置的行为MALLOC_CHECK_记录在“堆一致性检查”页面上

在使用 malloc、realloc 和 free 时检查和防止错误的另一种可能性是设置环境变量 MALLOC_CHECK_。当设置 MALLOC_CHECK_ 时,将使用一种特殊的(效率较低的)实现,该实现旨在容忍简单的错误,例如使用相同参数的两次 free 调用,或单个字节的溢出(off-by-one 错误)。然而,并非所有此类错误都可以得到保护,并且可能导致内存泄漏。如果 MALLOC_CHECK_ 设置为 0,任何检测到的堆损坏都会被静默忽略;如果设置为 1,则在 stderr 上打印诊断信息;如果设置为 2,则立即调用 abort。这可能很有用,因为否则可能会在很久以后发生崩溃,并且很难追查问题的真正原因。

于 2013-05-22T18:16:55.573 回答
1

要解决这个问题,你应该对glibc malloc 机制有所了解。如果您对此一无所知,可以阅读本文以了解 glibc malloc。

Glibc 使用bin来管理您释放的块,以避免频繁的系统调用。由于更频繁地分配和释放小内存空间这一事实,glibc 使用快速 bin(等距单链表)来管理小于global_max_fast(默认 64B 或 128B)的内存空间。glibc 所做的是将您释放的块的fd设置为指向快速 bin 指向的位置,并让快速 bin 指向该块。

free() 使用下一个相邻块的PREV_INUSE位来检查一个块是否被释放。但是,当您释放的块被添加到快速箱时,glibc 不会设置PREV_INUSE位。有一段代码可以检查一个fast bin的指针是否与你释放的chunk的指针相同。如果是,程序损坏,所以你不能释放一个指针两次,但你可以释放两个指针反过来。这里有一个简单的图表来帮助你理解。

当您有空时(a):

+++++++++++++++++++++++
+  16  +  24  +  32  +  ...
+++++++++++++++++++++++
          |
          |
          |
          |
          +--->+--------+
               |prevsize|
               +--------+
               |  size  |
               +--------+
               |fd=NULL |
               +--------+
               |  ...   |
               +--------+

当你有空时(b):

+++++++++++++++++++++++
+  16  +  24  +  32  +  ...
+++++++++++++++++++++++
          |
          |
          |
          |
          |    +--------+<---------+
          |    |prevsize|          |
          |    +--------+          |
          |    |  size  |          |
          |    +--------+          |
          |    |fd=NULL |          |
          |    +--------+          |
          |    |  ...   |          |
          +--->+--------+          |
               |prevsize|          |
               +--------+          |
               |  size  |          |
               +--------+          |
               |   fd   |----------+
               +--------+
               |  ...   |
               +--------+

当您再次释放(a)时:

+++++++++++++++++++++++
+  16  +  24  +  32  +  ...
+++++++++++++++++++++++
          |
          |
          |
          |
          +--->+--------+<---------+
               |prevsize|          |
               +--------+          |
               |  size  |          |
               +--------+          |
               |   fd   |---+      |
               +--------+   |      |
               |  ...   |   |      |
               +--------+<--+      |
               |prevsize|          |
               +--------+          |
               |  size  |          |
               +--------+          |
               |   fd   |----------+
               +--------+
               |  ...   |
               +--------+
于 2016-12-11T14:10:12.560 回答
0

Use Valgrind to detect double free or corruption. Check out the blog on using valgrind Link

于 2013-11-06T23:24:37.720 回答
0

我在代码中遇到了同样的问题,在功能测试中,我试图生成一个双释放 libc 异常,以测试日志记录功能。

事实证明,gcc 在为 Release 构建时已经优化了malloc()调用和两个free()调用(我认为是-O3)。

我尝试使用volatileona来解决该问题,但 gcc 不会传递volatile void*free. 然后我尝试分配a给 a volatile void* aa,但 gcc 仍然优化aaa消失了。我终于通过插入一条std::cout消息让它工作了。

char* a = malloc(24);
std::cout << "a is " << a << std::endl;
free(a);
free(a);

现在代码正确地“崩溃”了,并打印出所需的双释放消息和堆栈转储。

于 2016-11-29T23:45:41.257 回答