在指针上调用 free 两次

Calling free on a pointer twice

我在讲座中被教导,在指针上调用 free() 两次真的非常糟糕。我知道在释放它之后立即将指针设置为 NULL 是一种很好的做法。

然而,我仍然没有听到任何关于为什么会这样的解释。据我了解,malloc() 的工作方式在技术上应该跟踪它已分配并提供给您使用的指针。那么为什么它不知道它通过 free() 接收到的指针是否已被释放?

我很想了解,当您在之前已释放的位置上调用 free() 时,内部会发生什么。

当您使用 malloc 时,您是在告诉 PC 您想要在堆上为您保留一些内存位置。计算机返回指向 space.

的第一个字节的指针

当您使用 free 时,您实际上是在告诉计算机您不再需要 space,因此它将 space 标记为可用于其他数据。

指针仍然指向那个内存地址。此时,堆中的相同 space 可以由另一个 malloc 调用返回。当您第二次调用 free 时,您并没有释放以前的数据,而是释放了新数据,这可能对您的程序不利 ;)

回答你的第一个问题,

So why does it not know, whether a pointer it receives through free() has been freed yet or not?

因为 C 标准中 malloc() 的规范没有强制要求。当您调用 malloc() 或函数族时,它所做的是 return 您一个指针,并在内部存储分配给该指针 的内存位置的大小.这就是 free() 不需要大小来清理内存的原因。

此外,一旦 free()-d,实际上 分配的内存会发生什么仍然取决于实现。调用 free() 只是一个 标记 来指出分配的内存不再被进程使用,并且可以在需要时回收和重新分配。因此,此时跟踪分配的指针是非常没有必要的。 OS 保持 所有 回溯将是不必要的负担。

然而,出于调试目的,某些库实现可以为您完成这项工作,例如 DUMA 或 dmalloc,最后但并非最不重要的是来自 Valgrind 的 memcheck 工具。

现在,技术上C 标准不指定您在已释放的指针上调用 free() 时的任何行为。是 undefined behavior.

C11,章节 §7.22.3.3,free() 函数

[...] if the argument does not match a pointer earlier returned by a memory management function, or if the space has been deallocated by a call to free() or realloc(), the behavior is undefined.

当您调用 malloc 时,您会得到一个指针。运行时库需要跟踪 malloced 内存。通常 malloc 不会将内存管理结构与 malloc 编辑的内存分开存储,而是存储在一个地方。所以 x 字节的 malloc 实际上需要 x+n 字节,其中一种可能的布局是前 n 字节包含一个链表结构,该结构具有指向下一个(可能是前一个)分配的内存块的指针。

当你 free 一个指针时,函数 free 可以遍历它的内部内存管理结构,并检查你传入的指针是否是 malloced 的有效指针.只有这样它才能访问内存块的隐藏部分。但是做这个检查会非常耗时,特别是如果你分配了很多。所以 free 只是假设你传入了一个有效的指针。这意味着它直接访问内存块的隐藏部分,并假定那里的链表指针是有效的。

如果你 free 一个块两次那么你可能会遇到这样的问题,有人做了一个新的 malloc,得到你刚刚释放的内存,覆盖它,然后第二个 free 读取来自它的无效指针。

freed 指针设置为 NULL 是一种很好的做法,因为它有助于调试。如果您访问 freed 内存,您的程序可能会崩溃,但它也可能只是读取可疑值,然后可能会崩溃。找到根本原因可能会很困难。如果您将 freed 指针设置为 NULL,您的程序将在您尝试访问内存时立即崩溃。这在调试期间有很大帮助。

C 标准仅说明对 malloc 返回的指针调用 free 两次及其系列函数会调用未定义的行为。没有进一步解释为什么会这样。
但是,解释为什么不好 here

Freeing The Same Chunk Twice

To understand what this kind of error might cause, we should remember how the memory manager normally works. Often, it stores the size of the allocated chunk right before the chunk itself in memory. If we freed the memory, this memory chunk might have been allocated again by another malloc() request, and thus this double-free will actually free the wrong memory chunk - causing us to have a dangling pointer somewhere else in our application. Such bugs tend to show themselves much later than the place in the code where they occured. Sometimes we don't see them at all, but they still lurk around, waiting for an opportunity to rear their ugly heads.

Another problem that might occure, is that this double-free will be done after the freed chunk was merged together with neighbouring free chunks to form a larger free chunk, and then the larger chunk was re-allocated. In such a case, when we try to free() our chunk for the 2nd time, we'll actually free only part of the memory chunk that the application is currently using. This will cause even more unexpected problems.