1

试图理解我的问题的答案

当试图释放堆管理器分配的内存时会发生什么,它分配的比要求的多?

我写了这个函数并且对它的输出感到困惑

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。

4

7 回答 7

8

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

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

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

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

于 2010-02-25T19:16:02.977 回答
7

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

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

您从中获得的信号free很可能与您通过写入分配给您的内容(p即写入内存管理器用于跟踪内存块等的开销信息)而搞砸内存管理的事实有关。

于 2010-02-25T19:05:48.610 回答
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 终止符。

于 2010-02-25T19:17:00.087 回答
1

“p”的价值:

你分配了足够的空间来适应这个:“”

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

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

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

打印输出 [破损] "p"

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

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

释放“q”

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

于 2010-02-25T19:33:09.880 回答
0

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

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

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

于 2010-02-25T19:18:40.413 回答
0

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

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

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

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

因此,通过这个小小的解释,您现在可以理解为什么您的代码可以工作了。

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

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

够清楚了吧?

于 2010-02-25T19:33:20.197 回答
0

我建议阅读这篇介绍。

指针和内存

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

于 2010-02-25T19:55:20.973 回答