malloc() 的奇怪行为

Posted

技术标签:

【中文标题】malloc() 的奇怪行为【英文标题】:Weird behavior of malloc() 【发布时间】:2011-01-21 03:54:27 【问题描述】:

试图理解我的问题的答案

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。

【问题讨论】:

malloc 将您希望分配的字节数作为参数。 malloc(1) 将分配 1 个字节的内存。然后,您写入的内存远远超过一个字节,这会覆盖其他内存位置。 相关:***.com/questions/2022335/whats-the-point-in-malloc0 相关:***.com/questions/1941323/… 【参考方案1】:

您复制到*p 的字节数超过了分配的字节数,从而覆盖了分配空间后内存位置可能存在的任何内容。

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

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

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

【讨论】:

根据您原来的malloc 指向的确切位置,对strcpy 的调用也可能使您的程序崩溃。 这不是特定于实现的,它是未定义的。您可以指望特定于实现的行为,但不是未定义的。任何事情都有可能发生,编译器编写者不会觉得有义务对这样的程序做任何有用的事情。 澄清一下:这不是标准所称的实现定义的行为,这意味着实现者定义了应该发生的事情,它是未定义的行为,这意味着没有人承诺任何事情。实际发生的情况取决于实际的实现,这就是我所说的“特定于实现”的意思。不应将其解释为好像有一些随实现附带的规范说明了究竟会发生什么。 “依赖于实现”可能是一个更好的术语。我希望在相同条件下可重复的行为(即任何事情都可能发生,但一旦发生,我希望它每次都会发生)。例如,正如 rosuur 所示,该程序每次运行时都会以相同的方式失败。更改字符串常量,或传递给第二个malloc() 的大小,您将再次掷骰子。【参考方案2】:

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

通常,但取决于操作系统,内存是按“页”(即多个字节)分配的。另一方面,Malloc() 以更“细化”的方式从这些“页面”分配内存:通过malloc 管理与每个分配相关的“开销”。

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

【讨论】:

操作系统以页为单位提供内存;但是 malloc 在我所知道的任何健全的系统上都没有。你可以测试一下。 :) @fullreset: 嗯……我的投稿中已经提到了这一点,除非我不明白你的意思。 @fullreset:完全没有问题。干杯!【参考方案3】:

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

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

【讨论】:

【参考方案4】:

“p”的价值:

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

[[ 字符串以空结尾,记得吗?你看不到它,但它就在那里——所以这是一个字节用完了。 ]]

但您正在尝试存储:“01234556789abcdefghijklmnopqrstuvwxyz”

因此,结果是以“123..”开头的“东西”被存储在您分配的内存之外——可能会覆盖其他地方的其他“东西”。因此,您的结果会很混乱,正如“jidupont”所说,您很幸运它不会崩溃。

打印输出[破损]“p”

如前所述,您的写法已经超出了“p”的结尾;但 malloc 不知道这一点。因此,当您为“q”请求另一块内存时,它可能会按照它为“p”提供的内存为您提供内存;也许它对齐了内存(典型),所以它的指针被四舍五入到一个不错的数字;然后它可能会使用其中的一些内存来存储您不应该关心的簿记信息。但你不知道,是吗?你也不应该知道——你只是不应该写入你没有分配给自己的内存!

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

释放“q”

如果你释放“q”,然后尝试访问它——就像你尝试打印它一样——你(通常)会得到一个讨厌的错误。这是当之无愧的。你不应该取消注释“free(q)”。但你也不应该尝试打印“q”,因为你还没有放任何东西!据您所知,它可能包含乱码,因此 print 将继续运行,直到遇到 NULL ——这可能直到世界末日才会发生——或者,更有可能的是,直到你的程序访问更多它不应该的内存' t,并且由于操作系统对您不满意而崩溃。 :)

【讨论】:

【参考方案5】:

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

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

这正是 C 的方式。您可以很容易地滥用从 malloc 返回的内存区域,并且该语言不在乎。它只是假设在一个正确的程序中你永远不会这样做,而其他的一切都在等待。

【讨论】:

【参考方案6】:

Malloc 是一个和你一样的函数 :)

malloc 的实现有很多,所以我就不赘述无用的细节了。

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

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

所以有了这个小小的解释,您现在可以理解为什么您的代码可以工作了。

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

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

够清楚了吧?

【讨论】:

【参考方案7】:

我建议阅读这篇介绍。

Pointers And Memory

帮助我理解了栈和堆分配的区别,很好的介绍。

【讨论】:

以上是关于malloc() 的奇怪行为的主要内容,如果未能解决你的问题,请参考以下文章

Linux 堆结构和 malloc() 和 free() 的行为

C中奇怪的malloc行为

嵌入式系统上的 malloc 行为

parseInt() 函数的奇怪行为

removeFromParent() 奇怪的行为

QLcdNumber 奇怪的行为