两次调用时为什么会崩溃?

时间:2010-06-25 11:32:48

标签: c++ c

在C和C ++中,free(my_pointer)在被调用两次时崩溃。

为什么呢?每个malloc都有簿记和大小。当第一个free被调用时,它会识别出这个分配的大小,这就是为什么我们不需要传递大小和免费通话。

因为它知道每件事为什么不检查第二次并且什么都不做?

我不理解malloc/free行为或free没有安全实施。

5 个答案:

答案 0 :(得分:29)

你不能在未分配的内存上调用free,标准明确指出(略微转述,我的重点):

  

free函数导致其参数指向的空间被释放,即可用于进一步分配。如果参数是空指针,则不执行任何操作。否则,如果参数与先前由内存管理函数返回的指针不匹配,或者 ,如果通过调用free或realloc释放空间,则行为未定义。

例如,如果您在新块中间重新分配了双重释放的地址,并且分配它的代码恰好存储在那里看起来像真正的malloc块头的东西,会发生什么?像:

 +- New pointer    +- Old pointer
 v                 v
+------------------------------------+
|                  <Dodgy bit>       |
+------------------------------------+

混乱,就是这样。

内存分配功能就像电锯一样,如果你正确使用它们,你应该没有问题。但是,如果你滥用它们,后果就是你自己的错,无论是破坏记忆还是更糟,或者切断你的一只手臂: - )


关于评论:

  

...它可以优雅地与最终用户沟通关于双倍释放相同的位置。

如果没有保留所有mallocfree来电的记录,以确保您不会对一个块进行双重释放,那么我认为这不可行。这将需要巨大的开销,而仍然无法解决所有问题。

如果出现以下情况:

  • thread在地址42处分配和释放的内存。
  • 线程B为内存分配地址42并开始使用它。
  • 线程A第二次释放该内存。
  • 线程C为内存分配地址42并开始使用它。

然后你有线程B和C都认为他们拥有那个内存(这些不必是执行的线程,我在这里使用术语线程只是一段运行的代码 - 它可能都在一个执行线程,但顺序调用。)

不,我认为当前mallocfree只要您正确使用它们就可以了。无论如何都要考虑实现自己的版本,我认为没有错,但我怀疑你会遇到一些非常棘手的性能问题。


如果你希望在free周围实现自己的包装器,你可以使它更安全(以牺牲性能为代价),特别是{{1}以下调用:

myFreeXxx

代码的结果是你可以用一个指向你指针的指针来调用#include <stdio.h> #include <stdlib.h> void myFreeVoid (void **p) { free (*p); *p = NULL; } void myFreeInt (int **p) { free (*p); *p = NULL; } void myFreeChar (char **p) { free (*p); *p = NULL; } int main (void) { char *x = malloc (1000); printf ("Before: %p\n", x); myFreeChar (&x); printf ("After: %p\n", x); return 0; }

  • 释放记忆;和
  • 将指针设置为NULL。

后一位意味着,如果你试图再次释放指针,它将什么都不做(因为标准专门涵盖了释放NULL)。

不会保护您免受所有情况的影响,例如,如果您将指针复制到别处,请释放原件,然后释放副本:

myFreeXxx

如果您正在使用C11,那么现在有一种更好的方法,就是为C语言编译时函数重载而必须为每种类型显式调用不同的函数。您可以使用通用选择来调用正确的函数,同时仍允许类型安全:

char *newptr = oldptr;
myFreeChar (&oldptr);     // frees and sets to NULL.
myFreeChar (&newptr);     // double-free because it wasn't set to NULL.

这样,您只需拨打#include <stdio.h> #include <stdlib.h> void myFreeVoid (void **p) { free (*p); *p = NULL; } void myFreeInt (int **p) { free (*p); *p = NULL; } void myFreeChar (char **p) { free (*p); *p = NULL; } #define myFree(x) _Generic((x), \ int** : myFreeInt, \ char**: myFreeChar, \ default: myFreeVoid )(x) int main (void) { char *x = malloc (1000); printf ("Before: %p\n", x); myFree (&x); printf ("After: %p\n", x); return 0; } ,它就会根据类型选择正确的功能。

答案 1 :(得分:9)

你可能会误解其行为。如果它立即崩溃,那么 以安全的方式实现。我可以证明这对于free()许多月前来说并不常见。当时典型的CRT实现根本没有检查。它既快速又激烈,它只会破坏堆的内部结构,搞乱分配链。

在没有任何诊断的情况下,程序会在堆损坏发生后长时间行为不端或崩溃。如果没有任何提示,为什么它会以这种方式行为不端,那么崩溃的代码实际上并不是导致崩溃的原因。一个heisenbug,非常难以排除故障。

现代CRT或OS堆实现不再常见。这种未定义的行为非常可被恶意软件利用。它可以让您的生活变得更轻松,您可以快速找到代码中的错误。它让我在过去的几年中摆脱了麻烦,不必在很长一段时间内调试难以追踪的堆损坏。好事。

答案 2 :(得分:3)

  

为什么在第二次free()调用

时找不到任何分配的大小时,它不会第二次检查

free()函数本身的额外检查会在所有正确的情况下降低程序速度。你不应该做双重免费。管理记忆是你作为程序员的责任;不这样做是一个编程错误。它是C哲学的一部分:它为你提供了所需的所有力量,但结果却让你很容易在脚下射击。

许多C运行时会对其调试版本进行一些检查,因此如果您做错了什么,您将获得合理的通知。

答案 3 :(得分:3)

好问题。正如您所注意到的那样,malloc和free通常会在分配之前的几个字节中进行某种形式的簿记。但是这样想一想:

  1. Malloc一些记忆 - 添加了簿记数据。
  2. 释放它 - 将内存返回池中。
  3. 你或其他人malloc的更多内存,可能包括也可能不包括旧的分配。
  4. 你再次释放旧指针。
  5. 堆(malloc的免费管理代码)此时已经丢失了和/或覆盖了簿记数据,因为内存已经回到堆中了!

    因此崩溃了。提供此功能的唯一方法是记住在某个数据库中进行的每次分配,这种分配将无限增长。所以他们不这样做。相反,请记住不要双重自由。 :)

答案 4 :(得分:1)

你说:

  

不明白为什么。每个malloc()都有簿记和大小。

不是必须的。我将解释一下dlmalloc(用于glibc,uClibc,......)。

Dlmalloc跟踪可用空间块。不能有两个连续的空闲块,它们会立即合并。根本没有跟踪分配的块!分配的块有一些备用空间用于簿记信息(此块的大小,前一个块的大小和一些标志)。当分配的块是空闲的()时,dlmalloc将它插入到双向链表中。

当然,所有这些都可以在this dlmalloc article

更好地解释