为什么在两次调用时自由崩溃
在C和C ++中, free(my_pointer)
在被调用两次时崩溃。
为什么? 每个malloc
都有大小的簿记。 当第一个free
被调用时,它会识别出这个被分配的大小,这就是为什么我们不需要传递大小和免费通话。
因为它知道每件事为什么不检查第二次并且什么都不做?
我不明白malloc/free
行为或free
是不安全实现的。
你不允许在未分配的内存上free
拨打电话,标准明确指出(略微改述,我的重点):
free
函数导致其参数指向的空间被释放,即可用于进一步分配。 如果参数是空指针,则不执行任何操作。 否则,如果参数与先前由内存管理函数返回的指针不匹配,或者如果通过调用free或realloc释放了空间,则行为未定义。
例如,如果你在双重释放的地址已经在新块的中间重新分配并且分配它的代码恰好存储了那些看起来像真正的malloc块头的东西,会发生什么? 喜欢:
+- New pointer +- Old pointer vv +------------------------------------+ | | +------------------------------------+
混乱,那是什么。
内存分配function就像电锯一样,如果你正确使用它们,你应该没有问题。 但是,如果你滥用它们,后果是你自己的错,无论是破坏记忆还是更糟,或者切断你的一只手:-)
关于评论:
…它可以优雅地与最终用户沟通关于加倍相同的位置。
如果没有保留所有malloc
和free
通话的记录以确保你没有双重禁用一块,我不能认为这是可行的。 这将需要巨大的开销, 仍然无法解决所有问题。
如果:
- thread在地址42处分配和释放的内存。
- 线程B为内存分配地址42并开始使用它。
- 线程A第二次释放该内存。
- 线程C为内存分配了一个地址42并开始使用它。
然后你有线程B和C都认为他们拥有那个内存(这些不必是执行的线程,我在这里使用术语线程只是一段运行的代码 – 它可能都在一个线程中执行但按顺序调用)。
不,我认为目前的malloc
和free
都可以正常使用它们。 无论如何都要考虑实现自己的版本,我认为没有错,但我怀疑你会遇到一些非常棘手的性能问题。
如果你想要free
实现自己的包装器,你可以使它更安全(以牺牲性能为代价),特别是下面的myFreeXxx
调用:
#include #include 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; }
代码的结果是您可以使用指向指针的指针调用myFreeXxx
,它将同时:
- 释放记忆; 和
- 将指针设置为NULL。
后一位意味着,如果你试图再次释放指针,它将什么都不做(因为标准特别涵盖了释放NULL)。
它不会保护您免受所有情况的影响,例如,如果您将指针复制到别处,则释放原始文件,然后释放副本:
char *newptr = oldptr; myFreeChar (&oldptr); // frees and sets to NULL. myFreeChar (&newptr); // double-free because it wasn't set to NULL.
如果您正在使用C11,那么有一种更好的方法,因为现在C具有编译时函数重载,因此必须为每种类型显式调用不同的函数。 您可以使用通用选择来调用正确的函数,同时仍允许类型安全:
#include #include 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; }
有了它,你只需调用myFree
,它将根据类型选择正确的函数。
你可能会误解它的行为。 如果它立即崩溃,那么它将以安全的方式实施。 我可以certificate这对于free()许多月前来说并不常见。 当时典型的CRT实现根本没有检查。 速度与激情,它只会破坏堆的内部结构,搞乱分配链。
在没有任何诊断的情况下,程序会在堆损坏发生后长时间行为不端或崩溃。 没有任何暗示,为什么它会以这种方式行为不端,崩溃的代码实际上并不是导致崩溃的原因。 一个heisenbug,非常难以排除故障。
对于现代CRT或OS堆实现,这不再常见。 恶意软件可以很好地利用这种未定义的行为。 它会让您的生活变得更轻松,您可以快速找到代码中的错误。 它让我在过去的几年中摆脱了麻烦,不必在很长一段时间内调试难以追踪的堆损坏。 好事。
为什么不在第二次free()调用时找不到任何分配的大小时再检查
在所有正确的情况下,对free()
函数本身的额外检查会降低程序的速度。 你不应该做双重免费。 管理记忆是你作为程序员的责任; 不这样做是一个编程错误。 它是C哲学的一部分:它为你提供了所需的所有力量,但结果却让你很容易在脚下射击。
许多C运行时将对其调试版本进行一些检查,因此如果您做错了什么,您将获得合理的通知。
好问题。 正如您所注意到的,malloc和free通常会执行某种forms的簿记,通常在分配之前的几个字节中。 但是这样想一想:
- Malloc一些记忆 – 添加簿记数据。
- 释放它 – 将内存返回池中。
- 你或其他人malloc的更多内存,可能包括也可能不包括旧的分配。
- 你再次释放旧指针。
堆(malloc的代码是免费管理)此时已经丢失了和/或覆盖了簿记数据,因为内存已经回到堆中!
因此崩溃了。 提供此function的唯一方法是记住在某个地方的数据库中进行的每次分配,这种分配将无限制地增长。 所以他们不这样做。 相反,记住不要双重自由。 🙂
你说:
不明白为什么。 每个malloc()都有簿记和大小。
不是necesarilly。 我将解释一下dlmalloc(用于glibc,uClibc,……)。
Dlmalloc跟踪自由空间的块。 不能有两个连续的空闲块,它们会立即合并。 根本没有跟踪分配的块! 分配的块有一些备用空间用于簿记信息(此块的大小,前一个块的大小和一些标志)。 当分配的块是free()时,dlmalloc将其插入到双向链表中。
当然,所有这些都可以在这篇dlmalloc文章中得到更好的解释