C 在指针上调用free两次
我在课堂上被教导,对指针调用两次C 在指针上调用free两次,c,pointers,malloc,free,dynamic-memory-allocation,C,Pointers,Malloc,Free,Dynamic Memory Allocation,我在课堂上被教导,对指针调用两次free()是非常非常糟糕的。我知道,在释放指针之后立即将其设置为NULL,这是一种很好的做法 然而,我仍然没有听到任何关于这是为什么的解释。据我所知,按照malloc()的工作方式,它应该在技术上跟踪它分配给您使用的指针。那么,为什么它不知道它通过free()接收的指针是否已被释放 我很想了解,当您在以前已被释放的位置上调用free()时,内部会发生什么情况。当您使用malloc时,您是在告诉PC您想在堆上为您保留一些内存位置。计算机返回指向寻址空间的第一个字节
free()
是非常非常糟糕的。我知道,在释放指针之后立即将其设置为NULL
,这是一种很好的做法
然而,我仍然没有听到任何关于这是为什么的解释。据我所知,按照malloc()
的工作方式,它应该在技术上跟踪它分配给您使用的指针。那么,为什么它不知道它通过free()
接收的指针是否已被释放
我很想了解,当您在以前已被释放的位置上调用
free()
时,内部会发生什么情况。当您使用malloc
时,您是在告诉PC您想在堆上为您保留一些内存位置。计算机返回指向寻址空间的第一个字节的指针
当您使用free
时,实际上是在告诉计算机您不再需要该空间,因此它会将该空间标记为可用于其他数据
指针仍然指向该内存地址。此时,堆中的相同空间可以由另一个
malloc
调用返回。第二次调用free
时,释放的不是以前的数据,而是新数据,这可能对您的程序不利;) 要回答您的第一个问题
那么,为什么它不知道它通过free()
接收的指针是否已被释放
因为,C标准中的malloc()
规范并不强制要求这样做。调用malloc()
或函数族时,它会返回一个指针,并在内部存储分配给该指针的内存位置的大小。这就是free()
不需要大小来清理内存的原因
而且,一旦free()
-d,实际分配的内存将仍然依赖于实现。调用free()。因此,此时跟踪分配的指针是非常不必要的。保留所有的回溯将是操作系统不必要的负担
然而,出于调试目的,一些库实现可以为您完成这项工作,比如DUMA或dmalloc,最后但并非最不重要的是Valgrind的memcheck工具
现在,从技术上讲,C
标准没有指定在已经空闲的指针上调用free()
时的任何行为。是的
C11
,第§7.22.3.3章,free()
功能
[…]如果
参数与内存管理系统先前返回的指针不匹配
函数,或者如果已通过调用free()
或realloc()
释放空间,则
行为是未定义的
当您调用malloc
时,会得到一个指针。运行库需要跟踪malloc
ed内存。通常,malloc
不存储与malloc
ed内存分离的内存管理结构,而是存储在一个位置。因此x字节的malloc
实际上需要x+n个字节,其中一种可能的布局是前n个字节包含一个链表结构,该链表结构带有指向下一个(可能是上一个)分配内存块的指针
当您free
一个指针时,函数free
可以遍历它的内部内存管理结构,并检查您传入的指针是否为malloc
ed的有效指针。只有这样,它才能访问内存块的隐藏部分。但是做这个检查会非常耗时,特别是如果你分配了很多。所以free
只是假设您传入了一个有效的指针。这意味着它直接访问内存块的隐藏部分,并假设那里的链表指针有效
如果您两次释放
一个块,那么您可能会遇到这样的问题:有人执行了一个新的malloc
,获得了刚才释放的内存,并覆盖了它,第二个释放
从中读取无效指针
将free
d指针设置为NULL
是一种很好的做法,因为它有助于调试。如果访问free
d内存,您的程序可能会崩溃,但它也可能只是读取可疑值,稍后可能会崩溃。找到根本原因可能很难。如果将free
d指针设置为NULL
,当您试图访问内存时,程序将立即崩溃。这在调试过程中有很大帮助。C标准只规定对malloc
及其家族函数返回的指针调用两次free
,会调用未定义的行为。没有进一步解释为什么会这样。
但是,为什么它是坏的解释是:
两次释放同一块
要了解这种错误可能导致什么,我们应该记住内存管理器通常是如何工作的。通常,它会在内存中块本身之前存储所分配块的大小。如果我们释放了内存,这个内存块可能会被另一个malloc()
请求再次分配,因此这个双重释放实际上会释放错误的内存块-导致我们在应用程序的其他地方有一个悬空指针。这样的bug往往比它们出现在代码中的地方晚得多。有时我们根本看不到它们,但它们仍然潜伏在我们周围,等待着一个机会来展示它们丑陋的脑袋
另一个可能出现的问题是,在释放的块与相邻的空闲块合并形成一个更大的空闲块,然后重新分配较大的块之后,将执行此双重空闲。在里面