malloc()的奇怪行为
试着理解我的问题的答案
尝试释放堆管理器分配的内存时会发生什么,分配的内容超过了要求?
我写了这个函数,并对其输出感到困惑
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。
您正在将更多字节复制到*p
不是已分配,覆盖分配空间后可能存在于内存位置的任何内容。
当你再次调用malloc
时,它需要一部分内存,它知道此时未使用(这个时间恰好是*p
之后的几个字节),在那里写入一些簿记信息并返回一个指向该位置的新指针。
簿记信息malloc
写入恰好以’!’开头。 在此运行中,后跟一个零字节,因此您的第一个字符串被截断。 新指针恰好指向您之前覆盖的内存的末尾。
所有这些都是特定于实施的,并且可能导致每次运行或根据月相的不同结果。 对malloc()
的第二次调用也绝对是以可怕的方式崩溃程序的权利(特别是因为你可能会覆盖malloc
内部使用的内存)。
你这次只是幸运:这是一个未定义的行为,不指望它。
通常,但根据操作系统,内存以“页面”(即多个字节)分配。 另一方面, Malloc()
以更“细化”的方式从这些“页面”分配内存:与通过malloc
管理的每个分配相关联的“开销”。
你从free
获得的信号很可能与你通过写过你分配的内容来搞乱内存管理这一事实有关,即写入内存管理器使用的开销信息以跟踪内存块等。
这是一个经典的堆溢出。 p只有1个字节,但堆管理器填充了分配(在你的情况下是32个字节)。 q在p之后立即分配,因此它自然会获得下一个可用点。 例如,如果p的地址是0x1000,则分配给q的地址是0x1020。 这解释了为什么q指向字符串的一部分。
更有趣的问题是为什么p只是“01234556789abcdefghijklm”而不是“01234556789abcdefghijklmnopqrstuvwxyz”。 原因是内存管理器使用内部簿记的分配之间的差距。 从内存管理器的角度来看,内存布局如下:p D q其中D是内存管理器的内部数据结构(在我们的示例中为0x1010到0x1020)。 在为q分配内存时,堆管理器将其内容写入簿记区域(0x1010至0x1020)。 将字节更改为0会截断字符串,因为它被视为NULL终止符。
“p”的价值:
你分配了足够的空间来适应这个:“”
[[字符串空终止,还记得吗? 你没有看到它,但它就在那里 – 所以这是一个用完的字节。 ]]
但你试图存储这个:“01234556789abcdefghijklmnopqrstuvwxyz”
因此,结果是以“123 ..”开头的“东西”被存储在你分配的内存之外 – 可能是在其他地方写入其他“东西”。 因此,你的结果会很混乱,因为“jidupont”说你很幸运,它不会崩溃。
打印输出[BROKEN]“p”
如上所述,你已经写过了“p”的结尾; 但malloc不知道这一点。 所以当你要求“q”的另一块内存时,也许它会给你记忆,因为它为你提供了“p”; 也许它对齐了内存(典型值),所以它的指针被四舍五入到一些不错的数字; 然后它可能会使用一些内存来存储您不应该关注的簿记信息。 但你不知道,是吗? 你也不应该知道 – 你只是不应该写你自己没有分配的记忆!
结果呢? 你看到了你所期望的一些 – 但它被截断了! 因为……可能在您使用的内存中分配了另一个块(并且未经许可使用,我可能会添加),或其他拥有该块并更改它的内容,并且在任何情况下某些值都已更改 – 导致:“01234556789abcdefghijklm !”。 再次,幸运的是,事情并没有爆发。
释放“q”
如果你释放“q”,然后尝试访问它 – 正如你正在尝试打印它 – 你会(通常)得到一个讨厌的错误。 这是当之无愧的。 你不应该取消注释“free(q)”。 但你也不应该试着打印“q”,因为你还没有放任何东西! 所有你知道的,它可能包含乱码,因此打印将继续,直到遇到NULL – 这可能不会发生在世界末日 – 或者更可能,直到你的程序访问更多的内存,它应该’因为操作系统对你不满意而崩溃了。 🙂
故意滥用这些function会产生无意义的结果,这不应该令人费解。
连续两个mallocs不能保证连续两个内存区域。 malloc可以选择分配超过您请求的内存量,但如果分配成功则不会少。 当您选择覆盖未分配的内存时,程序的行为不能保证是可预测的。
这就是C的方式。 您可以轻松地滥用malloc返回的内存区域,语言无关紧要。 它只是假设在一个正确的程序中你永远不会这样做,其他一切都可以争夺。
Malloc就像你的function一样:)
有很多malloc实现,所以我不会讨论无用的细节。
在第一次调用malloc时,它会向系统询问内存。 例如,假设4096是标准内存页面大小,这是好的。 所以你调用malloc要求1个字节。 函数malloc将向系统请求4096个字节。 接下来,它将使用此内存的一小部分来存储内部数据,例如可用块的位置。 然后它将剪切该块的一部分并将其发送给您。
内部算法将在调用free之后尝试重用块,以避免重新向系统请求内存。
因此,通过这个小解释,您现在可以理解为什么您的代码正在工作。
你正在写内存,问我的malloc到系统。 此合并不会打扰系统,因为您保留在为进程分配的内存中。 问题是你无法确定你是不是在写软件内存的关键部分。 这种关闭错误称为缓冲区溢出,并导致大多数“神秘错误”。
避免它们的最好方法是在linux上使用valgrind。 这种软会告诉你,如果你正在写作或阅读你不应该的地方。
它够清楚吗?
我建议阅读这篇介绍。
指针和记忆
它帮助我理解了堆栈和堆分配之间的区别,非常好的介绍。