在指针上调用 free 两次

Posted

技术标签:

【中文标题】在指针上调用 free 两次【英文标题】:Calling free on a pointer twice 【发布时间】:2016-03-20 23:38:42 【问题描述】:

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

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

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

【问题讨论】:

指针只是你注释数据方向的地方,但你真正释放的是数据本身 @DanielSanchez 是的,但是由于 malloc 向您抛出了那个指针,内存释放/分配构造是否应该仍然注意到它已经释放了指针指向的位置? 如果在free 之后不使用指针,则将其设置为NULL 是没有用的。 如果你在free之后使用指针,无论你是否将它设置为NULL,你都有未定义的行为。当然,如果您检查 NULL,那么它会有所帮助,但是设置指向NULL 的指针并不是您绝对必须要做的事情,请根据具体情况进行取决于你如何使用指针。 可以搜索释放的块是否已经在空闲块列表中,但这会减慢不需要该功能的程序 可以把mallocfree管理的堆内存看成一滩水。当您使用malloc 分配内存时,您会得到一勺水,您可以随心所欲地使用它。当您free 内存时,如果将一勺水倒回池中,您就会忘记哪个内存是您的,内存管理器也是如此。释放内存只会清空你的勺子,但你仍然保留实际的勺子(指针)。 【参考方案1】:

当您使用malloc 时,您是在告诉PC 您想在堆上为您保留一些内存位置。计算机返回一个指向寻址空间的第一个字节的指针。

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

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

【讨论】:

感谢您的解释!【参考方案2】:

回答你的第一个问题,

那么为什么它不知道通过free()接收到的指针是否已经被释放了呢?

因为,C 标准中malloc() 的规范并未强制执行此操作。当您调用malloc() 或函数系列时,它所做的是返回一个指针并在内部存储分配的内存位置的大小in 该指针。这就是free() 不需要大小来清理内存的原因。

此外,一旦free()-d,实际分配的内存发生的情况仍然取决于实现。调用free() 只是一个标记,指出分配的内存不再被进程使用,如果需要,可以回收和重新分配。因此,此时跟踪分配的指针是非常不必要的。保持所有回溯对操作系统来说是不必要的负担。

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

现在,技术上C 标准没有指定任何行为,如果您在已经释放的指针上调用 free()。是undefined behavior。

C11,第 7.22.3.3 章,free() 函数

[...] 如果 该参数与内存管理先前返回的指针不匹配 函数,或者如果空间已通过调用 free()realloc() 释放,则 行为未定义。

【讨论】:

【参考方案3】:

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

两次释放同一个块

要了解这种错误可能导致什么,我们应该记住内存管理器通常是如何工作的。通常,它在内存中的块本身之前存储已分配块的大小。如果我们释放了内存,这个内存块可能已经被另一个 malloc() 请求再次分配,因此这个 double-free 实际上会释放错误的内存块 - 导致我们在某处有一个悬空指针其他在我们的应用程序中。此类错误往往比它们在代码中出现的位置晚得多。有时我们根本看不到它们,但它们仍然潜伏在周围,等待机会抬起丑陋的脑袋。

另外一个可能出现的问题是,这个double-free会在被释放的chunk和相邻的free chunk合并在一起形成一个更大的free chunk,然后再把更大的chunk重新-分配。在这种情况下,当我们第二次尝试free() 我们的块时,我们实际上只会释放应用程序当前正在使用的部分内存块。这会导致更多意想不到的问题。

【讨论】:

【参考方案4】:

当您调用malloc 时,您将获得一个指针。运行时库需要跟踪malloced 内存。通常malloc 不存储与malloc ed 内存分开的内存管理结构,而是在一个位置。因此,x 字节的malloc 实际上占用 x+n 字节,其中一种可能的布局是前 n 字节包含一个链表结构,其中包含指向下一个(也可能是上一个)分配的内存块的指针。

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

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

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

【讨论】:

以上是关于在指针上调用 free 两次的主要内容,如果未能解决你的问题,请参考以下文章

使用指向第一个成员的指针调用 free 是不是有效?

malloc 和 free 在 C 中是如何实现的? [复制]

调用释放已存储值的 char 指针时,free() 函数失败

调用 free() 包装器:取消引用类型双关指针将破坏严格别名规则

使用 STL 在共享指针中调用两次析构函数

释放堆栈