free和malloc如何在C中工作?

时间:2009-12-24 06:49:06

标签: c memory free malloc

我试图找出如果我试图从中间释放一个指针会发生什么 例如,请查看以下代码:

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

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

我遇到了一个未处理异常错误消息的崩溃。 我想了解为什么以及如何免费工作,这样我不仅知道如何使用它,而且还能够理解奇怪的错误和异常并更好地调试我的代码ץ

非常感谢

8 个答案:

答案 0 :(得分:99)

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

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

稍后,如果你释放()块但不“忘记”你的指针,你可能会在将来偶然尝试通过该指针访问数据,并且行为是未定义的。可能发生以下任何一种情况:

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

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

答案 1 :(得分:25)

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

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

最终,分配器必须包含有关块的元数据,至少需要在某处存储长度。

我将描述三种方法。

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

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

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

有可能某些类型的第二种,可能是任何类型的第三种类型的分配器实际上检测到你已经提升了指针和DTRT,尽管我怀疑是否有任何实现会将运行时刻录到这样做。

答案 2 :(得分:10)

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

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

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

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

答案 3 :(得分:8)

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

  

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

答案 4 :(得分:7)

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

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

答案 5 :(得分:5)

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

答案 6 :(得分:2)

永远不要这样做。

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

答案 7 :(得分:2)

摘自书:Understanding and Using C Pointers

  

分配内存时,附加信息将存储为堆管理器维护的数据结构的一部分。此信息包括块的大小,通常紧邻分配的块放置。