(C)堆分配器如何处理4字节块头,而只返回8的倍数的地址?

它似乎没有意义,除非我们忽略一个段开头的任何潜在的多余空间,然后让第一个分配的块位于8的第一个倍数(其对应的第一个头是该地址-4) 。 这将在未使用之前留下许多字节。 那是一般的做法吗?

编辑:感谢paxdiablo的详细说明如下。 这对16字节标题都有意义。 但是,我正在使用一个4字节的标题,看起来像这样:

struct mhdr { int size; // size of this block } tMallocHdr; 

现在,如果我的堆在一个8的倍数的地址上启动,并且malloc返回的任何地址需要是8的倍数,并且我需要使用4个字节的标题,我似乎被迫“浪费”第一个我的堆的4个字节。 例如:

 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 ^ (heap starts) 

如果堆从地址8处的胡萝卜开始,使用此示例中的寻址方案,我可以在malloc调用之后返回给用户的第一个可返回地址为16; 我需要4个字节的标头,第一个地址是8的倍数,允许4个字节的标头是16(标题从12开始)。 这意味着我浪费了内部堆内存的前4个字节来排队(8-11)。

这是可以接受的牺牲,还是我在考虑这个错误?

通常,在分配区域的块头中存在浪费的空间。 我看到的许多实现都使用了一个16字节(我在这里使用经典的字节定义,八位之一,而不是ISO C定义)头,紧接在malloc返回的地址之前,填充分配的区域到最后16个字节。

极大地简化了用于分配的算法,并且还保证返回的内存地址将针对体系结构进行适当的对齐。

但请记住,这是一个实现细节,而不是C标准的function。 如果没有对齐要求并且内存分配限制为255个字节,那么只浪费一个字节(尽管以更复杂的算法为代价)是非常合理的。


你可以拥有一个只能分配256字节块的嵌入式应用程序是非常合理的,在这种情况下你可以有一个简单的基于位图的分配器(位图存储在别处,而不是与分配的内存块一致),浪费每个块只有一位(我们之前在低内存环境中完成了这一点)。

或者,您可能在大型地址空间和内存环境中拥有一个分配器,无论您要求什么,它都能为您提供4G。 然后没有标题的浪费,但可能很多填充:-)

或者你可能从特定尺寸的竞技场中获得一块(竞技场A为1-64字节,竞技场B为65-128等)。 这意味着不需要标头,但仍然允许可变尺寸(最大)和比上述4G解决方案少得多的浪费。

底线,这取决于实施。


在一个(相当简单的) malloc实现中,你可以有一个双重链接的“块”列表,由分配器给出。 这些块由头部和数据部分组成,为确保数据部分的对齐正确,头部必须在16字节边界上,并且是16字节长度的倍数(这是16字节对齐要求 – 您的实际要求可能不那么严格)。

此外,块本身被填充,因此它是16字节的倍数,因此下一个块也适当地对齐。

这保证了任何块的数据部分,即给予malloc调用者的地址,都是正确对齐的。

所以你可能会在那个区域浪费掉。 标头(具有4字节整数和指针)可能只是:

 struct mhdr { int size; // size of this block. struct mhdr *prev; // prev chunk. struct mhdr *next; // next chunk. int junk; // for padding. } tMallocHdr; 

意味着这16个字节中的4个将被浪费掉。 有时,这是满足其他要求(对齐)所必需的,并且在任何情况下,您都可以将该填充空间用于其他目的。 正如一位评论者指出的那样,保护字节可用于检测某些forms的竞技场腐败:

 struct mhdr { int guard_bytes; // set to 0xdeadbeef to detect some corruptions. int size; // size of this block. struct mhdr *prev; // prev chunk. struct mhdr *next; // next chunk. } tMallocHdr; 

而且,虽然这种填充在技术上是浪费空间,但只有它占整个舞台的相当大比例才变得很重要。 如果你要分配4K块内存,那么四个字节的浪费只是总大小的千分之一。 实际上,作为用户的浪费可能是标题的整个16个字节,因为那是你不能使用的内存,所以它大约是0.39%(16 /(4096 + 16))。

这就是为什么malloc的链接字符列表是一个非常糟糕的主意 – 你倾向于使用每个字符:

  • 16个字节的标头。
  • 字符为1个字节(假设为8位字符)。
  • 15个字节的填充。

这将使您的0.39%数字变为96.9%的浪费(31 /(31 + 1))。


并且,在回答您的进一步问题时:

这是可以接受的牺牲,还是我在考虑这个错误?

我会说,是的,这是可以接受的。 通常,您分配较大的内存块,其中四个字节不会对宏观方案产生影响。 正如我之前所说,如果你分配了很多小东西,这不是一个好的解决方案,但这不是malloc的一般用例。

某些分配器没有任何已分配块的标头。 有些消除了小块的标题。 除了安全性之外,根本不需要分配块的头部。 这可以通过其他方式实现,例如,通过与分配的存储器完全物理分离的管理数据。

即使是类型化的内存分配器也可以消除标头。 参见例如BIBOP 。

您可能会发现,如果返回的块始终在8字节边界上对齐,则实现使用8字节块来保存任何控制信息。 我相信K&R(第一版和第二版)的实施都是这样做的。 实际上,它使用指针和大小,因此它有8个字节的控制信息。 如果它使用8字节对齐的块,那么这样做没有真正的惩罚。 (对于64位机器,最严格的对齐可能是16字节 – 例如它在SPARC上 – 因此块开销有点大,但基本上是相同的原因。)