动态内存分配器

Dynamic Memory Allocator

给出以下问题,并提供以下答案:

如何计算绿色轮廓区域中的值?我相信我对 C 中的 free() 函数如何工作以及它的作用有相当深刻的理解:清除堆堆栈上动态分配的内存块(完全删除它,或使其免费使用,以未来分配)。

我不明白的是,对 free(0x400b010) 的调用如何仅更改上面的其他一些堆块? (我用绿色勾勒出的那些)。我得到地址 0x400b010(二进制值:01000000 00001011 01100000 00011100 不会改变,根据它已经释放的分配,在其 bit 0 中有 0

谁能给我解释一下?例如,当在上面的块上调用 free 时,地址 0x400b00c:0x000000013 处的块将其值(: 之后的第二个参数)更改为 0x00000022。这个例子只是一个奇怪的例子,之前分配的块(1 in bit 0)变为空闲,即使没有在该地址上调用空闲。

类似的一些块改变了它们的值,而另一些则没有。

我尝试以多种不同的方式处理这个示例,但我无法破解为什么解决方案看起来像这样,所以我希望这里有人能向我解释到底发生了什么。

malloc返回的指针(然后传递给free)指向块的用户内容的第一个字节,NOT在 header(或页脚)处。所以要在free中找到块的header,需要从指针参数(header的大小)中减去4。

所以当free(0x400b010)被调用时,第一件事是减去4并得到0x400b00c处块的header——也就是0x13。这告诉您该块是 16 字节 (0x10) 并且正在使用中(位 0 已设置)并且前一个块正在使用中(位 1 已设置)。作为释放它的一部分,您需要检查相邻块是否空闲。位 1 的值告诉你前一个块不是空闲的,但要检查下一个块,你需要找到它。为此,您将大小 (0x10) 添加到 header 地址 (0x400b00c) 以获得下一个块的 header 地址 (0x400b01c),这将为您提供 header 值 0x12。该块是空闲的,因此您将它的大小添加到当前块,并通过将当前块的 header 设置为 0x22 将当前块标记为空闲(因此它现在是一个空闲的 32 字节块)。您现在还需要找到这个新合并块的页脚(在 header 地址 + 大小 - 8 == 0x400b024)并将其也更改为 0x22。

没有必要更改块的旧页脚或以下正在合并的空闲块的旧 header,因为它们现在是空闲块内容的一部分(它们是"don't care" 个值)。也没有必要触摸前一个块,因为它(仍在)使用中。

此设置有些奇怪。

  • 在位 1 中记录前一个块的 free/in 使用状态是不必要的复杂化。您也可以通过查看前一个块的页脚来检查它。如果它是免费的,无论如何你都需要前一个块的页脚值来找到它的大小(和 header)。
  • 您确实需要知道堆的开始和结束,以免在检查上一个或下一个块时不小心离开堆的末端。如果这是堆上的最后一个块,尝试获取下一个块的 header 会导致错误,因为您是堆末尾的 运行。

找了图,没找到满意的。我就试着用文字来解释吧。

对比一下左右的table,你会看到变化的盒子是0x400b00c,0x400b028,分别从13和12变成了22

注意每个内存块都有页眉和页脚。由于free是在0x400b010进行的,说明0x400b00c是header。因此,下面的所有条目(400affc ~ 400b008) 都将保持不变,因为它不受自由操作的影响。

从0x400b00c往上看,之前有2块:

  • 区块 1: 0x400b00c ~ 0x400b018
  • 区块 2: 0x400b01c ~ 0x400b028

请注意,块 2 未被使用,因为 bit 0 indicates the use of current block,但值 0x0000012 是偶数。因此,如果 block 1 被释放,block 1 和 block 2 将合并为一个新的未使用的块。

这里发生的是这个合并过程将尽可能高效地执行。因此,块 1 的前一个页脚和块 2 的前一个页眉 将保持不变,因为它们不需要。释放内存空间不需要初始化。

因此,唯一需要更改的是新块的新页眉和页脚,位于位置 0x400b00c 和 0x400b028。

请注意,新页眉和页脚(含)之间有 8 个块,导致 8 * 4 = 32 字节。 32 的二进制是 100000,但由于 前一个块(未更改的块)正在使用中,因此位 1 设置为 1。结果为 100010,十六进制为 22。

抱歉,如果此解释令人困惑,请询问您是否无法理解此答案的任何部分。