调用指针两次会导致免费错误

21

我在讲座中学习到,对指针调用free()两次非常不好。我知道释放完指针后,将其设置为NULL是一个好习惯。

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

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


指针只是标注数据的地址,但实际释放的是数据本身。 - Netwave
@DanielSanchez 是的,但由于malloc将指针传递给您,因此内存释放/分配结构仍应注意到它是否已释放指针所指向的位置。 - Joe
实现malloc的常见方法是存储一个空闲块列表。如果您将一个已经在列表中的块添加到该列表中,那么就会出现一些有趣的问题。例如,未来的分配可能会找到该块的旧条目并对其进行分配,然后您就处于块既被分配又在空闲块列表中的状态,因此它可能会再次被分配。 - M.M
2
可以搜索以查看释放的块是否已在空闲块列表中,但这会减慢不需要此功能的程序。 - M.M
1
你可以将由mallocfree管理的堆内存看作是一池水。当你使用malloc分配内存时,你会得到一勺水,你可以随意处理它。当你释放内存时,这勺水就会倒回池中,你会失去哪些内存是你自己的跟踪,内存管理器也是如此。释放内存只是清空了你的勺子,但你仍然保留着实际的勺子(指针)。 - Some programmer dude
显示剩余5条评论
4个回答

35
当你使用malloc时,你告诉计算机你想要在堆上保留一些内存位置,专为你使用。计算机返回指向该地址空间第一个字节的指针。
当你使用free时,实际上是在告诉计算机你不再需要该空间,所以它将这个空间标记为可用于其他数据。
指针仍然指向那个内存地址。此时,在堆中的同一空间可以被另一个malloc调用返回。当你第二次调用free时,你不是释放之前的数据,而是新的数据,这可能不利于你的程序 ;)

1
谢谢您的解释! - Joe

6

回答你的第一个问题,

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

因为,在 C 标准中,malloc() 的规范并没有要求这样做。当您调用 malloc() 或其相关函数时,它会返回一个指针,并在内部存储分配的内存位置的大小 该指针中。这就是为什么 free() 不需要大小来清理内存的原因。

此外,一旦使用 free() 进行释放,实际上分配的内存发生了什么仍然取决于实现。调用 free() 只是一个标记,指出分配的内存不再被进程使用,可以在需要时重新获取和重新分配。因此,在那个时候跟踪分配的指针是非常不必要的。这将对操作系统保留所有回溯记录是一个不必要的负担。

然而,为了调试目的,一些库实现可以帮你完成这项工作,比如DUMA或dmalloc,最后但并非最不重要的是来自Valgrind的memcheck工具。

从技术上讲C标准没有指定在已经释放的指针上调用free()会发生什么行为。这是未定义的行为

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

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


4
C标准只规定了在使用malloc及其相关函数返回的指针调用free两次会导致未定义行为,但并没有进一步解释原因。
然而,这里解释了为什么这样做是错误的:

重复释放同一块内存

要理解这种错误可能会引起什么问题,我们应该记住内存管理器通常的工作方式。通常情况下,它会在内存中的块之前存储已分配块的大小。如果我们释放了内存,那么这个内存块可能被另一个malloc()请求重新分配,因此这个重复释放实际上会释放错误的内存块 - 导致我们在应用程序的其他地方拥有悬空指针。这些错误往往比它们发生的代码位置晚得多。有时我们根本看不到它们,但它们仍然潜伏着,等待机会露出丑陋的头。

另一个可能发生的问题是,在将已释放的块与相邻的空闲块合并以形成较大的空闲块,然后重新分配较大的块之后,我们尝试第二次free()我们的块时,我们实际上只释放了应用程序当前使用的内存块的一部分。这将引起更多意外的问题。


2
当你调用malloc时,你得到一个指针。运行库需要跟踪malloc的内存。通常malloc不会将内存管理结构与malloc的内存分开存储,而是在同一个地方。所以对于x字节的malloc实际上需要x+n字节,其中一种可能的布局是前n个字节包含链表结构,指向下一个(和前一个)已分配的内存块。
当你free一个指针时,函数free会遍历其内部内存管理结构,并检查传递给它的指针是否是已经malloc的有效指针。只有这样它才能访问内存块中隐藏的部分。但是这样做的检查将非常耗时,特别是如果你分配了很多内存。因此,free简单地假定你传递了一个有效指针。这意味着它直接访问内存块的隐藏部分,并假定那里的链表指针是有效的。
如果你两次free一个内存块,那么你可能会遇到这样的问题:有人进行了新的malloc,得到了你刚释放的内存,覆盖它,第二次free从其中读取无效的指针。
将已free的指针设置为NULL是一个好习惯,因为它有助于调试。如果你访问了已free的内存,你的程序可能会崩溃,但它也可能只是读取可疑的值,然后可能在稍后崩溃。那么找到根本原因可能很困难。如果你将已释放的指针设置为NULL,当你尝试访问该内存时,你的程序将立即崩溃。这在调试期间非常有帮助。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接