malloc()的奇怪行为

时间:2010-02-25 19:05:00

标签: c free malloc heap

试图理解我的问题的答案

what happens when tried to free memory allocated by heap manager, which allocates more than asked for?

我编写了这个函数,并对其输出感到困惑

int main(int argc,char **argv){
  char *p,*q;
  p=malloc(1); 
  strcpy(p,"01234556789abcdefghijklmnopqrstuvwxyz"); //since malloc allocates atleast 1 byte
  q=malloc(2);
  //    free(q);
  printf("q=%s\n",q);
  printf("p=%s\n",p);

  return 0;
}

输出

q=vwxyz
p=01234556789abcdefghijklm!

任何人都可以解释这种行为吗?或者这个实现是否具体?

如果free(q)被取消注释,我将获得SIGABRT。

7 个答案:

答案 0 :(得分:8)

您正在将更多字节复制到*p而不是已分配,覆盖分配空间后可能存在于内存位置的任何内容。

当你再次调用malloc时,它需要一部分内存,它知道此时未使用(这恰好是*p之后的几个字节),写了一些簿记信息在那里并返回一个指向该位置的新指针。

簿记信息malloc写入恰好以'!'开头在此运行中,后跟一个零字节,因此您的第一个字符串被截断。新的指针发生在你之前覆盖的内存的末尾。

所有这些都是特定于实施的,可能会导致每次运行或根据月相的不同结果。对malloc()的第二次调用也绝对是以可怕的方式崩溃程序的权利(特别是因为你可能会覆盖malloc在内部使用的内存。)

答案 1 :(得分:7)

这次你很幸运:这是一种未定义的行为,不指望它。

通常,但是根据OS,存储器被分配在“页面”中(即多个字节)。另一方面,Malloc()以更“细化”的方式从这些“页面”中分配内存:通过malloc管理的每个分配都存在“开销”。

您从free获得的信号很可能与您通过使用p写过您分配的内容来搞乱内存管理这一事实相关,即写入由{{1}}使用的开销信息。内存管理器,以跟踪内存块等。

答案 2 :(得分:4)

这是一个经典的堆溢出。 p只有1个字节,但堆管理器填充分配(在你的情况下是32个字节)。 q在p之后立即分配,因此它自然地获得下一个可用点。例如,如果p的地址是0x1000,则分配给q的地址是0x1020。这解释了为什么q指向字符串的一部分。

更有趣的问题是为什么p只是“01234556789abcdefghijklm”而不是“01234556789abcdefghijklmnopqrstuvwxyz”。原因是内存管理器使用内部簿记分配之间的差距。从内存管理器的角度来看,内存布局如下:   p D q 其中D是内存管理器的内部数据结构(在我们的示例中为0x1010到0x1020)。在为q分配内存时,堆管理器将其内容写入簿记区域(0x1010至0x1020)。将字节更改为0会截断字符串,因为它被视为NULL终止符。

答案 3 :(得分:1)

“p”的价值:

你分配了足够的空间来容纳这个:“”

[[字符串空终止,还记得吗?你没有看到它,但它就在那里 - 所以这是一个用完的字节。 ]

但你试图存储这个:“01234556789abcdefghijklmnopqrstuvwxyz”

因此,结果是以“123 ..”开头的“东西”被存储在你分配的内存之外 - 可能是在其他地方写入其他“东西”。因此,你的结果将是凌乱的,因为“jidupont”说你很幸运它不会崩溃。

打印输出[BROKEN]“p”

如上所述,你已经写过了“p”的结尾;但malloc不知道这一点。所以当你为“q”要求另一块内存时,也许它会给你记忆,因为它给了你“p”;也许它对齐了内存(典型值),所以它的指针被四舍五入到一些不错的数字;然后它可能会使用一些内存来存储您不应该关注的簿记信息。但你不知道,是吗?你也不应该知道 - 你只是不应该写你没有自己分配的记忆!

结果呢?你看到了你所期望的一些 - 但它被截断了!因为......可能在您使用的内存中分配了另一个块(并且未经许可使用,我可能会添加),或其他拥有该块并更改它的内容,并且在任何情况下某些值都已更改 - 导致:“01234556789abcdefghijklm !”。再次,幸运的是,事情并没有爆发。

释放“q”

如果您释放“q”,然后尝试访问它 - 正如您正在尝试打印它 - 您将(通常)得到一个讨厌的错误。这是当之无愧的。你不应该取消评论“免费(q)”。但你也不应该试着打印“q”,因为你还没有放任何东西!所有你知道的,它可能包含乱码,因此打印将继续,直到遇到NULL - 这可能直到世界末日才会发生 - 或者更可能,直到你的程序访问更多的内存,它应该'因为操作系统对你不满意而崩溃了。 :)

答案 4 :(得分:0)

故意滥用这些功能会产生荒谬的结果,这不应该令人费解。

两个连续的mallocs不能保证连续两个内存区域。 malloc可以选择分配超过您请求的内存量,但如果分配成功则不会少。当您选择覆盖未分配的内存时,程序的行为无法保证是可预测的。

这就是C的方式。您可以轻松地滥用malloc返回的内存区域,语言无关紧要。它只是假设在一个正确的程序中你永远不会这样做,而其他一切都是为了争夺。

答案 5 :(得分:0)

Malloc就像你的功能一样:)

有很多malloc实现,所以我不会讨论无用的细节。

在第一次调用malloc时,它向系统询问内存。对于示例,假设4096是标准内存页面大小,这是好的。所以你调用malloc要求1个字节。函数malloc将向系统请求4096个字节。接下来,它将使用该存储器的一小部分来存储内部数据,例如可用块的位置。然后它将剪切该块的一部分并将其发回给您。

内部算法将在调用free之后尝试重用块,以避免重新向系统请求内存。

因此,通过这个小解释,您现在可以理解为什么您的代码正在运行。

你正在写内存,问我的malloc到系统。此合并不会打扰系统,因为您保留在为进程分配的内存中。问题是你无法确定你是不是在写软件内存的关键部分。这种关闭错误称为缓冲区溢出,并导致大多数“神秘错误”。

避免它们的最好方法是在linux上使用valgrind。如果你正在写作或阅读你不应该的地方,这种软会告诉你。

这很清楚吗?

答案 6 :(得分:0)

我建议阅读这篇介绍。

Pointers And Memory

它帮助我理解了堆栈和堆分配之间的区别,非常好的介绍。