1

这似乎没有意义,除非我们只是忽略段开头的任何潜在多余空间,然后让第一个分配的块位于 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)。

这是一个可以接受的牺牲,还是我想错了?

4

3 回答 3

6

通常,分配区域的块头中存在浪费的空间我见过的许多实现在从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;

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

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.

于 2010-06-01T03:13:43.597 回答
1

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

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

于 2010-06-01T03:15:44.157 回答
1

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

于 2010-06-01T03:17:17.753 回答