在C和C ++中,free(my_pointer)
在被调用两次时崩溃。
为什么呢?每个malloc
都有簿记和大小。当第一个free
被调用时,它会识别出这个分配的大小,这就是为什么我们不需要传递大小和免费通话。
因为它知道每件事为什么不检查第二次并且什么都不做?
我不理解malloc/free
行为或free
没有安全实施。
答案 0 :(得分:29)
你不能在未分配的内存上调用free
,标准明确指出(略微转述,我的重点):
free
函数导致其参数指向的空间被释放,即可用于进一步分配。如果参数是空指针,则不执行任何操作。否则,如果参数与先前由内存管理函数返回的指针不匹配,或者 ,如果通过调用free或realloc释放空间,则行为未定义。 强>
例如,如果您在新块中间重新分配了双重释放的地址,并且分配它的代码恰好存储在那里看起来像真正的malloc块头的东西,会发生什么?像:
+- New pointer +- Old pointer
v v
+------------------------------------+
| <Dodgy bit> |
+------------------------------------+
混乱,就是这样。
内存分配功能就像电锯一样,如果你正确使用它们,你应该没有问题。但是,如果你滥用它们,后果就是你自己的错,无论是破坏记忆还是更糟,或者切断你的一只手臂: - )
关于评论:
...它可以优雅地与最终用户沟通关于双倍释放相同的位置。
如果没有保留所有malloc
和free
来电的记录,以确保您不会对一个块进行双重释放,那么我认为这不可行。这将需要巨大的开销,而仍然无法解决所有问题。
如果出现以下情况:
然后你有线程B和C都认为他们拥有那个内存(这些不必是执行的线程,我在这里使用术语线程只是一段运行的代码 - 它可能都在一个执行线程,但顺序调用。)
不,我认为当前malloc
和free
只要您正确使用它们就可以了。无论如何都要考虑实现自己的版本,我认为没有错,但我怀疑你会遇到一些非常棘手的性能问题。
如果你做希望在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)。
不会保护您免受所有情况的影响,例如,如果您将指针复制到别处,请释放原件,然后释放副本:
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通常会在分配之前的几个字节中进行某种形式的簿记。但是这样想一想:
堆(malloc的免费管理代码)此时已经丢失了和/或覆盖了簿记数据,因为内存已经回到堆中了!
因此崩溃了。提供此功能的唯一方法是记住在某个数据库中进行的每次分配,这种分配将无限增长。所以他们不这样做。相反,请记住不要双重自由。 :)
答案 4 :(得分:1)
你说:
不明白为什么。每个malloc()都有簿记和大小。
不是必须的。我将解释一下dlmalloc(用于glibc,uClibc,......)。
Dlmalloc跟踪可用空间块。不能有两个连续的空闲块,它们会立即合并。根本没有跟踪分配的块!分配的块有一些备用空间用于簿记信息(此块的大小,前一个块的大小和一些标志)。当分配的块是空闲的()时,dlmalloc将它插入到双向链表中。
当然,所有这些都可以在this dlmalloc article
更好地解释