提问人:user238082 提问时间:12/24/2009 最后编辑:Vijay Mathewuser238082 更新时间:6/9/2015 访问量:84866
free 和 malloc 如何在 C 中工作?
How do free and malloc work in C?
问:
我试图弄清楚如果我尝试“从中间”释放指针会发生什么 例如,查看以下代码:
char *ptr = (char*)malloc(10*sizeof(char));
for (char i=0 ; i<10 ; ++i)
{
ptr[i] = i+10;
}
++ptr;
++ptr;
++ptr;
++ptr;
free(ptr);
我收到崩溃,并显示未处理的异常错误消息。 我想了解 free 为什么以及如何工作,这样我不仅知道如何使用它,而且能够理解奇怪的错误和异常,并更好地调试我的代码ץ
多谢
答:
这是未定义的行为 - 不要这样做。只有从 获得的指针,在此之前从不调整它们。free()
malloc()
问题是必须非常快,所以它不会尝试找到你调整后的地址所属的分配,而是尝试将调整后的地址的块返回到堆中。这会导致未定义的行为 - 通常是堆损坏或程序崩溃。free()
评论
从 http://opengroup.org/onlinepubs/007908775/xsh/free.html
free() 函数导致 ptr 指向的空间被解除分配;也就是说,可用于进一步分配。如果 ptr 为 null 指针,则不执行任何操作。否则,如果参数与之前由 calloc()、malloc()、realloc() 或 valloc() 函数返回的指针不匹配,或者如果通过调用 free() 或 realloc() 来释放空间,则行为未定义。 任何引用释放空间的指针的使用都会导致未定义的行为。
评论
大多数(如果不是全部)实现将在您正在操作的实际指针之前查找要释放几个字节的数据量。
进行百搭会导致内存映射损坏。free
如果你的示例,当你分配 10 个字节的内存时,系统实际上保留了 14 个字节。前 4 个包含您请求的数据量 (10),然后返回值 是指向分配的 14 个字节中第一个字节的未使用数据的指针。malloc
当您调用此指针时,系统将向后查找 4 个字节,以知道它最初分配了 14 个字节,以便知道要释放多少字节。此系统会阻止您提供要释放的数据量作为其自身的额外参数。free
free
当然,其他实现/可以选择其他方式来实现这一点。但是,它们通常不支持与 返回的指针或等效函数不同的指针。malloc
free
free
malloc
评论
当你对一个块进行恶意定位时,它实际上分配的内存比你要求的要多一些。这个额外的内存用于存储信息,例如分配块的大小,以及指向块链中下一个可用/已用块的链接,有时还有一些“保护数据”,以帮助系统检测您是否写入了分配块的末尾。此外,大多数分配器会将总大小和/或内存部分的起始时间四舍五入为字节的倍数(例如,在 64 位系统上,它可能会将数据对齐为 64 位(8 字节)的倍数,因为对于处理器/总线来说,从未对齐地址访问数据可能更加困难和低效), 因此,您最终也可能会得到一些“填充”(未使用的字节)。
当您释放指针时,它会使用该地址来查找它添加到已分配块开头(通常)的特殊信息。如果你传入一个不同的地址,它将访问包含垃圾的内存,因此它的行为是未定义的(但最常见的是会导致崩溃)
稍后,如果你释放了块,但没有“忘记”你的指针,你可能会在将来意外地尝试通过该指针访问数据,并且行为是未定义的。可能会出现以下任何一种情况:
- 内存可能被放在一个空闲块列表中,所以当你访问它时,它仍然恰好包含你留在那里的数据,并且你的代码正常运行。
- 内存分配器可能已经将内存(部分)给了程序的另一部分,然后可能会覆盖(部分)旧数据,因此当您读取它时,您会得到垃圾,这可能会导致代码出现意外行为或崩溃。或者,您将覆盖其他数据,导致程序的另一部分在将来的某个时间点表现得很奇怪。
- 内存可能已返回到操作系统(不再使用的内存“页”可以从地址空间中删除,因此该地址上不再有任何可用的内存 - 本质上是应用程序内存中未使用的“洞”)。当应用程序尝试访问数据时,将发生硬内存故障并终止进程。
这就是为什么确保在释放指针所指向的内存后不使用指针很重要的原因 - 最佳做法是在释放内存后将指针设置为 NULL,因为您可以轻松地测试 NULL,并且尝试通过 NULL 指针访问内存将导致错误但一致的行为, 这更容易调试。
评论
您释放了错误的地址。通过更改 ptr 的值,可以更改地址。free 无法知道它应该尝试释放从 4 个字节开始的块。保持原始指针完好无损,而不是纵的指针。正如其他人指出的那样,做你正在做的事情的结果是“不确定的”......因此,未处理的异常。
您可能知道您应该准确地传回您收到的指针。
因为 free() 一开始并不知道你的区块有多大,所以它需要辅助信息才能从其地址中识别原始区块,然后将其返回到自由列表中。它还将尝试将小的释放块与邻居合并,以产生更有价值的大自由块。
最终,分配器必须具有有关块的元数据,至少需要将长度存储在某处。
我将介绍三种方法。
一个明显的地方是将其存储在返回的指针之前。它可以分配一个比请求的块大几个字节的块,将大小存储在第一个单词中,然后返回指向第二个单词的指针。
另一种方法是保留一个单独的映射,至少描述分配块的长度,使用地址作为键。
实现可以从地址派生一些信息,也可以从地图派生一些信息。4.3BSD 内核分配器(我认为称为“McKusick-Karel 分配器”)对小于页面大小的对象进行 2 次幂分配,并且只保留每页大小,使给定页面的所有分配都具有单一大小。
对于某些类型的第二种分配器,以及可能任何类型的第三种类型的分配器,实际上都可以检测到您已经推进了指针和 DTRT,尽管我怀疑是否有任何实现会为此烧毁运行时。
永远不要这样做。
您释放了错误的地址。通过更改 ptr 的值,可以更改地址。free 无法知道它应该尝试释放从 4 个字节开始的块。保持原始指针完好无损,而不是纵的指针。正如其他人指出的那样,做你正在做的事情的结果是“不确定的”......因此出现未处理的异常
摘自《理解和使用 C 指针》一书
分配内存时,附加信息将作为堆管理器维护的数据结构的一部分进行存储。除其他外,此信息包括块的大小,并且通常紧挨着分配的块。
上一个:为什么 malloc 在 gcc 中将值初始化为 0?
下一个:什么是内存堆?
评论