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

vik*_*ain 18 c c++

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

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

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

要么我不理解malloc/free行为,要么free没有安全实施.

pax*_*blo 30

你不被允许打电话free给未分配的内存,标准明确指出(略微转述,我的重点):

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

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

 +- New pointer    +- Old pointer
 v                 v
+------------------------------------+
|                  <Dodgy bit>       |
+------------------------------------+
Run Code Online (Sandbox Code Playgroud)

混乱,那是什么.

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


关于评论:

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

如果没有记录所有mallocfree调用以确保你没有双重释放块,我不能认为这是可行的.这将需要巨大的开销,仍然无法解决所有问题.

如果:

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

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

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


如果你想围绕实现自己的包装free,你可以把它更安全(在一点点的性能损失为代价),特别是与像的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;
}
Run Code Online (Sandbox Code Playgroud)

代码的结果是你可以myFreeXxx使用指向你指针的指针进行调用,它将同时:

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

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

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

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

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

#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;
}
Run Code Online (Sandbox Code Playgroud)

有了它,你只需调用myFree它,它将根据类型选择正确的函数.

  • @vikas如果我们想要`free`来检查双重释放,那么`free`的实现需要跟踪每个被释放的地址,并且每次检查所有这些地址.更不用说与曾经被释放然后用`malloc`重新分配的地址合理地处理.您基本上建议标准C库的行为类似于valgrind.有时候通过valgrind运行一个程序,看看它有多快.这就是为什么除非你提出要求,否则不会进行这种记账. (3认同)

Han*_*ant 9

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

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

对于现代CRT或OS堆实现,这不再常见.恶意软件可以很好地利用这种未定义的行为.它可以让您的生活变得更轻松,您可以快速找到代码中的错误.这使我出于对过去几年的麻烦,还没有调试下落不明堆损坏在很长一段时间.好事.