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

Ton*_*ark 1 c heapalloc

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

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

struct mhdr {
    int size;  // size of this block
} tMallocHdr;
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

pax*_*blo 6

一般来说,在所分配的区域的块标题浪费的空间.我看到的许多实现在返回的地址之前使用了一个16字节(我在这里使用经典的字节定义,八位之一,而不是ISO C定义)标头malloc,并填充分配的区域最后也是16个字节.

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

但请记住,这是一个实现细节,而不是C标准的功能.如果没有对齐要求并且内存分配限制为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;
Run Code Online (Sandbox Code Playgroud)

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

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;
Run Code Online (Sandbox Code Playgroud)

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

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

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

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


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

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

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

  • @hatorade:我认为你遇到的一个问题是,你已经假设"你正在考虑"malloc`的"方式"是唯一可行的方法.换句话说,没有必要保持一个"4字节标题"与返回的分配保持一致,虽然这肯定是一种方式,但绝对不是唯一的方法.避免你选择的方式的一个潜在原因是任何"超出分配的意外写入"都不太可能破坏竞技场.一些实现具有一种模式,该模式放置特殊的"保护字节"来检查它. (3认同)