66

我试图弄清楚如果我尝试“从中间”释放一个指针会发生什么,例如,看看下面的代码:

char *ptr = (char*)malloc(10*sizeof(char));

for (char i=0 ; i<10 ; ++i)
{
    ptr[i] = i+10;
}
++ptr;
++ptr;
++ptr;
++ptr;
free(ptr);

我因未处理的异常错误消息而崩溃。我想了解免费工作的原因和方式,以便我不仅知道如何使用它,而且能够理解奇怪的错误和异常并更好地调试我的代码ץ

非常感谢

4

8 回答 8

122

当你 malloc 一个块时,它实际上分配的内存比你要求的多一点。这个额外的内存用于存储信息,例如分配块的大小,以及到块链中下一个空闲/使用块的链接,有时还有一些“保护数据”,可以帮助系统检测你是否写过去您分配的块的结尾。此外,大多数分配器会将总大小和/或内存部分的开头四舍五入为字节的倍数(例如,在 64 位系统上,它可能会将数据对齐为 64 位(8 字节)的倍数,如对于处理器/总线来说,从非对齐地址访问数据可能更加困难和低效),因此您最终可能还会得到一些“填充”(未使用的字节)。

当您释放指针时,它使用该地址来查找它添加到已分配块的开头(通常)的特殊信息。如果你传入一个不同的地址,它将访问包含垃圾的内存,因此它的行为是不确定的(但最常见的是会导致崩溃)

稍后,如果您 free() 块但没有“忘记”您的指针,您将来可能会不小心尝试通过该指针访问数据,并且行为未定义。可能会出现以下任何一种情况:

  • 内存可能会放在空闲块列表中,因此当您访问它时,它仍然恰好包含您留在那里的数据,并且您的代码正常运行。
  • 内存分配器可能已将(部分)内存分配给程序的另一部分,然后可能会覆盖(部分)您的旧数据,因此当您读取它时,您会得到可能导致意外行为的垃圾或从您的代码中崩溃。或者您将覆盖其他数据,导致程序的其他部分在将来的某个时间点出现奇怪的行为。
  • 内存可能已返回给操作系统(您不再使用的内存“页面”可以从您的地址空间中删除,因此该地址不再有任何可用内存 - 本质上是一个未使用的“洞”在您的应用程序的内存中)。当您的应用程序尝试访问数据时,将发生硬内存故障并终止您的进程。

这就是为什么确保在释放指针指向的内存后不使用指针很重要的原因——最佳实践是在释放内存后将指针设置为 NULL,因为您可以轻松测试 NULL,并且尝试通过 NULL 指针访问内存将导致错误但一致的行为,这更容易调试。

于 2009-12-24T06:55:04.690 回答
28

您可能知道您应该准确地传回您收到的指针。

因为 free() 起初并不知道你的块有多大,所以它需要辅助信息才能从其地址中识别出原始块,然后将其返回到空闲列表。它还将尝试将已释放的小块与邻居合并,以产生更有价值的大空闲块。

最终,分配器必须有关于你的块的元数据,至少它需要将长度存储在某个地方。

我将描述三种方法来做到这一点。

  • 一个明显的地方是将它存储在返回的指针之前。它可以分配一个比请求大几个字节的块,将大小存储在第一个字中,然后返回一个指向第二个字的指针。

  • 另一种方法是保留一个单独的映射,该映射至少描述已分配块的长度,使用地址作为键。

  • 一个实现可以从地址中获取一些信息,从地图中获取一些信息。4.3BSD 内核分配器(我认为称为“McKusick-Karel 分配器”)对小于页面大小的对象进行二次幂分配,并且只保留每页大小,从给定页面进行所有分配单一尺寸。

使用某些类型的第二种分配器和可能的任何类型的第三种分配器可能会实际检测到您已经推进了指针和DTRT,尽管我怀疑是否有任何实现会消耗运行时这样做。

于 2009-12-24T07:43:47.993 回答
14

大多数(如果不是全部)实现将在您正在操作的实际指针之前查找数据量以释放几个字节。狂野free会导致内存映射损坏。

如果您的示例,当您分配 10 个字节的内存时,系统实际上保留了 14 个字节。前 4 个包含您请求的数据量(10),然后的返回值malloc是指向第一个字节的指针分配的 14 个未使用的数据。

当您调用free此指针时,系统将向后查找 4 个字节以知道它最初分配了 14 个字节,以便它知道要释放多少。该系统会阻止您将要释放的数据量作为其自身的额外参数提供free

当然,malloc/的其他实现free可以选择其他方式来实现。但它们通常不支持与返回的指针或等效函数free不同的指针。malloc

于 2009-12-24T06:53:54.220 回答
8

来自http://opengroup.org/onlinepubs/007908775/xsh/free.html

free()函数导致ptr指向的空间被释放;也就是说,可供进一步分配。如果 ptr 是空指针,则不会发生任何操作。否则,如果参数与 calloc()、malloc()、realloc() 或 valloc() 函数先前返回的指针不匹配,或者如果空间通过调用 free() 或 realloc() 释放,则行为未定义。任何指向已释放空间的指针的使用都会导致未定义的行为。

于 2009-12-24T06:53:12.707 回答
7

这是未定义的行为 - 不要这样做。只有free()从 获得的指针malloc(),在此之前从不调整它们。

问题是free()必须非常快,所以它不会尝试找到调整后的地址所属的分配,而是尝试将调整后地址的块返回到堆中。这会导致未定义的行为——通常是堆损坏或程序崩溃。

于 2009-12-24T06:52:33.667 回答
5

你释放了错误的地址。通过更改 ptr 的值,您可以更改地址。free 无法知道它应该尝试释放从 4 个字节开始的块。保持原始指针的完整性和自由,而不是被操纵的指针。正如其他人指出的那样,做你正在做的事情的结果是“未定义的”......因此是未处理的异常。

于 2009-12-24T07:24:47.980 回答
3

永远不要这样做。

你释放了错误的地址。通过更改 ptr 的值,您可以更改地址。free 无法知道它应该尝试释放从 4 个字节开始的块。保持原始指针的完整性和自由,而不是被操纵的指针。正如其他人指出的那样,做你正在做的事情的结果是“未定义的”......因此未处理的异常

于 2010-02-17T06:23:28.467 回答
2

摘自本书:理解和使用 C 指针

分配内存时,附加信息作为堆管理器维护的数据结构的一部分存储。除其他事项外,此信息包括块的大小,并且通常紧邻分配的块放置。

于 2015-06-09T13:30:15.280 回答