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