为什么我会遇到C语言malloc断言失败?

112

我正在实现一个分治多项式算法,以便将其与OpenCL实现进行基准测试,但我无法使malloc正常工作。当我运行程序时,它会分配一些东西,检查一些内容,然后将size/2发送到算法中。然后当我再次执行malloc行时,它就会输出以下错误:

malloc.c:3096: sYSMALLOc: Assertion `(old_top == (((mbinptr) (((char *) &((av)->bins[((1) - 1) * 2])) - __builtin_offsetof (struct malloc_chunk, fd)))) && old_size == 0) || ((unsigned long) (old_size) >= (unsigned long)((((__builtin_offsetof (struct malloc_chunk, fd_nextsize))+((2 * (sizeof(size_t))) - 1)) & ~((2 * (sizeof(size_t))) - 1))) && ((old_top)->size & 0x1) && ((unsigned long)old_end & pagemask) == 0)' failed.
Aborted

需要翻译的内容是:

问题中的那一行代码是:

int *mult(int size, int *a, int *b) {
    int *out,i, j, *tmp1, *tmp2, *tmp3, *tmpa1, *tmpa2, *tmpb1, *tmpb2,d, *res1, *res2;
    fprintf(stdout, "size: %d\n", size);

    out = (int *)malloc(sizeof(int) * size * 2);
}

我使用 fprintf 检查了大小,它是一个正整数(通常在该点为 50)。我也尝试使用数字调用 malloc,但仍然出现错误。我不知道发生了什么,目前在谷歌上找到的信息都没有帮助。

有什么想法吗?我正在尝试编译更新的 GCC,以确定是否是编译器错误,但我真的很怀疑这一点。


我怀疑问题实际上出现在那一行之前。也许是双重释放的问题? - Mitch Wheat
int *mult(int size, int *a, int *b) {int *out,i, j, *tmp1, *tmp2, *tmp3, *tmpa1, *tmpa2, *tmpb1, *tmpb2,d, *res1, *res2;fprintf(stdout, "size: %d\n", size);out = (int *)malloc(sizeof(int) * size * 2); 程序的第三行: - Chris
8个回答

124

99.9%的可能性是您的内存已经损坏(超出或下溢缓冲区,释放后写入指针,在同一指针上两次调用free等)

Valgrind下运行代码以查看程序执行错误的位置。


3
Valgrind确实有帮助。我将旧的Matlab代码抄写错误了,有一个for循环迭代了j,然后在其中执行了j++,这导致它大部分地覆盖了它正在写入的数组,并以某种方式导致malloc失败。 感谢您的帮助! - Chris
1
Valgrind正是我在遇到这个错误时所需要的工具,它帮助我弄清楚了发生了什么。谢谢你提到它。 - alexwells
请查看下面的答案,其中使用了地址检查器(Address Sanitizer),以便更轻松地使用和更好的指导/说明。[/a/53923332/5958455] - iBug
我遇到了同样的问题。在我的情况下,我修改了一个头文件,但它没有重新编译。运行"make clean"或者删除所有的.o文件,然后重新编译。希望能帮到你。 - undefined

104

为了更好地让您理解为什么会发生这种情况,我想稍微扩展一下@r-samuel-klatchko的答案。

当您调用malloc时,实际上发生的事情比给您一块可供使用的内存要复杂得多。在底层,malloc还会保留一些有关其提供给您的内存的信息(最重要的是其大小),以便在您调用free时,它知道要释放多少内存。此信息通常保存在由malloc返回给您的内存位置之前。可以在互联网™上找到更详尽的信息,但基本概念类似于:

+------+-------------------------------------------------+
+ size |                  malloc'd memory                +
+------+-------------------------------------------------+
       ^-- location in pointer returned by malloc

在此基础上(且大大简化问题),当您调用malloc时,它需要获取指向可用内存下一部分的指针。最简单的方法之一是查看先前提供的内存的上一位,并将size字节向下(或向上)移动。使用这种实现方式,分配p1p2p3后,您的内存看起来像这样:

+------+----------------+------+--------------------+------+----------+
+ size |                | size |                    | size |          +
+------+----------------+------+--------------------+------+----------+
       ^- p1                   ^- p2                       ^- p3

那么,是什么导致了你的错误?

好的,想象一下你的代码错误地写入了超出你分配的内存量(可能是因为你分配的比你需要的少,这就是你的问题,或者是因为你在代码中某处使用了错误的边界条件)。假设你的代码向 p2 写入了大量数据,它开始覆盖 p3size 字段。当你下次调用 malloc 时,它会查看它返回的最后一个内存位置,查看其大小字段,移动到 p3 + size,然后从那里开始分配内存。然而,由于你的代码已经覆盖了 size,所以这个内存位置不再是之前分配的内存。

不用说,这可能会造成严重破坏!malloc 的实现者因此加入了许多“断言”或检查,试图进行一堆健全性检查,以捕捉如果它们即将发生的情况(以及其他问题)。在你的情况下,这些断言被违反了,因此 malloc 中止运行,并告诉你你的代码正要做一些它真的不应该做的事情。

正如先前所述,这是一种过于简单的说明,但足以说明问题。 malloc 的 glibc 实现超过 5k 行,已经有大量的研究关于如何构建良好的动态内存分配机制,因此在 SO 答案中涵盖所有内容是不可能的。希望这给你提供了一些关于实际造成问题的看法!


2
这真的很有帮助! - H.Potter

29

使用Valgrind以外的解决方案:

我非常高兴,因为我刚刚帮助我的朋友调试了一个程序。他的程序出现了这个确切的问题(malloc()导致中止),并且和GDB给出的错误信息相同。

我使用Address Sanitizer编译了他的程序。

gcc -Wall -g3 -fsanitize=address -o new new.c
              ^^^^^^^^^^^^^^^^^^

然后运行gdb new。当程序由后续的malloc()引发的SIGABRT终止时,会打印出大量有用的信息:

=================================================================
==407==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x6060000000b4 at pc 0x7ffffe49ed1a bp 0x7ffffffedc20 sp 0x7ffffffed3c8
WRITE of size 104 at 0x6060000000b4 thread T0
    #0 0x7ffffe49ed19  (/usr/lib/x86_64-linux-gnu/libasan.so.4+0x5ed19)
    #1 0x8001dab in CreatHT2 /home/wsl/Desktop/hash/new.c:59
    #2 0x80031cf in main /home/wsl/Desktop/hash/new.c:209
    #3 0x7ffffe061b96 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x21b96)
    #4 0x8001679 in _start (/mnt/d/Desktop/hash/new+0x1679)

0x6060000000b4 is located 0 bytes to the right of 52-byte region [0x606000000080,0x6060000000b4)
allocated by thread T0 here:
    #0 0x7ffffe51eb50 in __interceptor_malloc (/usr/lib/x86_64-linux-gnu/libasan.so.4+0xdeb50)
    #1 0x8001d56 in CreatHT2 /home/wsl/Desktop/hash/new.c:55
    #2 0x80031cf in main /home/wsl/Desktop/hash/new.c:209
    #3 0x7ffffe061b96 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x21b96)

让我们来看一下输出结果,特别是堆栈跟踪:

第一部分显示在new.c:59发生了无效的写入操作。这行代码读取:

memset(len,0,sizeof(int*)*p);
             ^^^^^^^^^^^^

第二部分说坏写入发生的内存是在new.c:55处创建的。该行代码如下:


if(!(len=(int*)malloc(sizeof(int)*p))){
                      ^^^^^^^^^^^

就这样。花费不到半分钟的时间我就找到了那个困扰我的朋友几个小时的错误。他成功地找到了错误,但是问题出在后续的malloc()调用上,之前的代码中没有发现这个错误。

总结:尝试使用GCC或Clang的-fsanitize=address选项。在调试内存问题时,它非常有帮助。


2

2
我收到了与你类似的以下信息:
    program: malloc.c:2372: sysmalloc: Assertion `(old_top == (((mbinptr) (((char *) &((av)->bins[((1) - 1) * 2])) - __builtin_offsetof (struct malloc_chunk, fd)))) && old_size == 0) || ((unsigned long) (old_size) >= (unsigned long)((((__builtin_offsetof (struct malloc_chunk, fd_nextsize))+((2 *(sizeof(size_t))) - 1)) & ~((2 *(sizeof(size_t))) - 1))) && ((old_top)->size & 0x1) && ((unsigned long) old_end & pagemask) == 0)' failed.

在使用malloc时,我在一些方法调用之前犯了一个错误。在将unsigned char数组添加字段后,通过更新sizeof()-operator之后的因子时,我错误地用'+'代替了乘法符号'*'。

以下是导致错误的代码:

    UCHAR* b=(UCHAR*)malloc(sizeof(UCHAR)+5);
    b[INTBITS]=(某些计算);
    b[BUFSPC]=(某些计算);
    b[BUFOVR]=(某些计算);
    b[BUFMEM]=(某些计算);
    b[MATCHBITS]=(某些计算);

稍后在另一个方法中,我再次使用了malloc,然后出现了上面显示的错误消息。调用如下(非常简单):

    UCHAR* b=(UCHAR*)malloc(sizeof(UCHAR)*50);

我认为在第一次调用时使用'+'符号,导致与立即初始化数组后的误算相结合(覆盖未分配给数组的内存),混淆了malloc的内存映射。因此,第二次调用失败了。


0
我们之所以出现这个错误,是因为我们忘记了乘以sizeof(int)。请注意,malloc(..)的参数是字节数,而不是机器字或其他什么东西的数量。

0

我有同样的问题,在一个循环中一遍又一遍地使用 malloc 来添加新的 char * 字符串数据。我遇到了相同的问题,但是在释放分配的内存 void free() 后问题得到了解决。


-1
我正在将一个应用程序从Visual C移植到Linux上的gcc,并且我遇到了同样的问题:

malloc.c:3096: sYSMALLOc: 使用UBUNTU 11上的gcc时进行断言。

我将相同的代码移植到Suse发行版(在其他计算机上),并且没有任何问题。
我怀疑问题不在我们的程序中,而在libc本身。

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