free():没有检测到双重自由

时间:2013-05-22 18:03:27

标签: c free glibc

free()函数存在一些问题:

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

有一个“ * glibc检测到* ./a.out:双重免费或损坏”。 这并不奇怪,因为我曾两次使用free()

但现在如果我这样做:

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

我的计算机没有错误,没有双重免费错误消息,但正如您所看到的,我的代码中有双重免费...

你知道出了什么问题吗?

4 个答案:

答案 0 :(得分:2)

我无法回答为什么在你的第二个例子中没有检测到双重自由,除了猜测libc的主要目标是默认执行。因此,检查像double-free这样的东西可能不是默认情况下库会花费大量精力进行检测的。

但是,如果运行MALLOC_CHECK_环境变量设置为1的程序,则会对stderr进行诊断(对于中止,将变量设置为2,对于具有诊断和中止的中止,将变量设置为3)写入stderr}的crashdump信息:

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

(注意环境变量名称的尾部下划线)。

"Heap Consistency Checking" page记录了使用MALLOC_CHECK_配置的libc堆检查的行为:

  

检查和防范使用中的错误的另一种可能性   malloc,realloc和free是设置环境变量   MALLOC_CHECK_。设置MALLOC_CHECK_时,特殊(效率较低)   使用的实现是为了容忍简单   错误,例如使用相同参数进行双重调用,或者   单字节溢出(逐个错误)。并非所有这些错误都可以   但是,可以防止内存泄漏。如果   MALLOC_CHECK_设置为0,任何检测到的堆损坏都是静默的   忽略;如果设置为1,则在stderr上打印诊断;如果设置为2,   立即调用中止。这可能很有用,因为否则a   崩溃可能发生得更晚,问题的真正原因是   然后很难追查。

答案 1 :(得分:1)

要解决这个问题,您应该了解glibc malloc机制。如果您对此一无所知,可以阅读this以了解glibc malloc的一般知识。

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

free()使用下一个相邻块的 PREV_INUSE 位来检查是否释放了一个块。但是,当您释放的块添加到快速bin时,glibc不会设置 PREV_INUSE 位。有一段代码可以检查快速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   |----------+
               +--------+
               |  ...   |
               +--------+

答案 2 :(得分:0)

使用Valgrind检测双重释放或损坏。查看有关使用valgrind Link

的博客

答案 3 :(得分:0)

我的代码中存在同样的问题,在功能测试中,我尝试生成双重释放的libc异常,以测试日志记录功能。

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

我尝试在volatile上使用a来解决该问题,但gcc不会将volatile void*传递给free。然后我尝试将a分配给volatile void* aa,但gcc仍然优化了aaa。我终于通过插入std::cout消息来实现它。

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

现在代码正确“崩溃”,因为我想要它,并打印出所需的双重免费消息和堆栈转储。