Tre*_*rey 1 c malloc memory-management glibc
我试图了解 glibc 的 malloc 在我的 64 位机器上到底是如何进行簿记的。
根据文档,它在块之前存储实际大小(malloc 值加上簿记字节)。所以我从这里获取了以下代码:
int *a = (int *) malloc(4);
int *b = (int *) malloc(7);
int *c = (int *) malloc(1);
int *d = (int *) malloc(32);
int *e = (int *) malloc(4);
printf("0x%x\n", a);
printf("0x%x\n", b);
printf("0x%x\n", c);
printf("0x%x\n", d);
printf("0x%x\n", e);
printf("a[-1] = %d, a[-2] = %d\n", a[-1], a[-2]);
printf("b[-1] = %d, b[-2] = %d\n", b[-1], b[-2]);
printf("c[-1] = %d, c[-2] = %d\n", c[-1], c[-2]);
printf("d[-1] = %d, d[-2] = %d\n", d[-1], d[-2]);
printf("e[-1] = %d, e[-2] = %d\n", e[-1], e[-2]);
Run Code Online (Sandbox Code Playgroud)
产生:
0xfca042a0
0xfca042c0
0xfca042e0
0xfca04300
0xfca04330
a[-1] = 0, a[-2] = 33 // letter[-2] is how much memory malloc has actually allocated
b[-1] = 0, b[-2] = 33
c[-1] = 0, c[-2] = 33
d[-1] = 0, d[-2] = 49
e[-1] = 0, e[-2] = 33
Run Code Online (Sandbox Code Playgroud)
所以你可以看到前三个地址相距 32 个字节,这是有道理的,因为 malloc 分配的最小块是 32 或者更确切地说4 * sizeof(void*)。但是,当我分配 32 个字节时,下一个块是 48 个字节,而不是 64 个字节,这是为什么呢?
如果 malloc 分配了 32 和 48 字节,为什么它分别打印 33 和 49?
块的内部 glibc 表示结构如下:
struct malloc_chunk {
INTERNAL_SIZE_T mchunk_prev_size; /* Size of previous chunk (if free). */
INTERNAL_SIZE_T mchunk_size; /* Size in bytes, including overhead. */
struct malloc_chunk* fd; /* double links -- used only if free. */
struct malloc_chunk* bk;
/* Only used for large blocks: pointer to next larger size. */
struct malloc_chunk* fd_nextsize; /* double links -- used only if free. */
struct malloc_chunk* bk_nextsize;
};
Run Code Online (Sandbox Code Playgroud)
mchunk_prev_size除和之外的每个字段mchunk_size仅在块空闲时填充。这两个字段位于用户可用缓冲区之前。该mchunk_prev_size字段保存前一个块的大小(仅当它是空闲的时),而该mchunk_size字段始终保存该块的实际大小(比请求的大小至少多 16 个字节)。此外,为了节省空间,该mchunk_prev_size字段实际上可以驻留在前一个块内(因为它是空闲的)。
块始终与 16 字节边界对齐(即,它们的十六进制地址始终以 结尾0)。mchunk_prev_size最小分配大小为 16(对较小大小的请求将向上舍入为 16),并且始终需要 16 个额外字节mchunk_size(每个字节 8 个字节)。
所以现在您可能可以猜到第一个问题的答案:
[...] malloc 分配的最小块是 32 或者更确切地说
4 * sizeof(void*)。但是,当我分配 32 个字节时,下一个块是 48 个字节,而不是 64 个字节,这是为什么呢?
嗯,是的,最小的块大小是 32,但增量实际上是 16。因此,您可以使用 16 的倍数且大于或等于 32 的任何大小。如果您请求的大小在 17 到 32 之间,您将得到48 字节的块(其中 32 个可用于用户数据)。此外,最小malloc分配大小与 没有太大关系sizeof(void *),它与sizeof(size_t)real 更相关(正如您的链接也指出的那样)。
示例中分配后的堆状态如下:
struct malloc_chunk {
INTERNAL_SIZE_T mchunk_prev_size; /* Size of previous chunk (if free). */
INTERNAL_SIZE_T mchunk_size; /* Size in bytes, including overhead. */
struct malloc_chunk* fd; /* double links -- used only if free. */
struct malloc_chunk* bk;
/* Only used for large blocks: pointer to next larger size. */
struct malloc_chunk* fd_nextsize; /* double links -- used only if free. */
struct malloc_chunk* bk_nextsize;
};
Run Code Online (Sandbox Code Playgroud)
从上图中不清楚的一件事是,如上所述,实际上mchunk_prev_size可以驻留在前一个块内以节省空间。
为了进一步阐明这一点,我们还可以看一下执行以下代码后的堆布局:
+-----------+ 0xfca04290
| prev size |
|-----------|
| size |
a --> |-----------| 0xfca042a0
| user data |
| |
+-----------+ 0xfca042b0
| prev size |
|-----------|
| size |
b --> |-----------| 0xfca042c0
| user data |
| |
+-----------+ 0xfca042d0
| prev size |
|-----------|
| size |
c --> |-----------| 0xfca042e0
| user data |
| |
+-----------+ 0xfca042f0
| prev size |
|-----------|
| size |
d --> |-----------| 0xfca04300
| user data |
| |
| |
| |
+-----------+ 0xfca04320
| prev size |
|-----------|
| size |
e --> |-----------| 0xfca04330
| user data |
| |
+-----------+
Run Code Online (Sandbox Code Playgroud)
对于 和a,b将为mchunk_size32。 的mchunk_prev_size将会b与 的最后 8 个字节重叠a。尽管如此,两个块都将对齐到 16 字节。
void *a = malloc(24); // 0x123400a0
void *b = malloc(16); // 0x123400c0
Run Code Online (Sandbox Code Playgroud)
在不与的最后 8 个字节重叠mchunk_prev_size的情况下,为了保持 16 字节的对齐,在 的结尾和开头之间需要留一些空白baab之间留一些空白空间(正好 8 个字节),这个空间将是浪费了。
现在,进入第二个问题:
如果 malloc 分配了 32 和 48 字节,为什么它分别打印 33 和 49?
由于每个块的大小必须是 16 字节的倍数,因此该大小的最低 4 位(一个十六进制数字)将保持未使用状态。malloc节省空间并使用它们来存储有关块的附加信息。最后 3 位实际上是malloc内部使用的标志。这在源代码注释中也有解释:
+-----------------------+ 0x12340090
| prev size |
|-----------------------|
| size |
a --> |-----------------------| 0x123400a0
| user data |
| |
+-----------------------+ 0x123400b0
| user data / prev size |
+-----------------------+ 0x123400b8
| size |
b --> |-----------------------| 0x123400c0
| user data |
| |
+-----------------------+ 0x123400d0
Run Code Online (Sandbox Code Playgroud)
这些标志位A|M|P是:
A:非主要竞技场块。M: 块是mmap编辑。P:前一个块正在使用中(即不是空闲的)。您可以在 malloc 源代码中找到对上述内容更全面的解释。
由于您的所有块仍在使用中,因此在您看到的大小字段中size | PREV_IN_USE。由于“先前使用的”( P) 是最低有效位,因此它具有将大小值增加 1 的效果,因此您可以看到33而不是32例如。
一些附加说明:
如果您想检查块的大小,您应该使用size_t而不是int,如下所示:
void *a = malloc(32);
size_t *ptr = a;
size_t chunk_size = ptr[-1] & ~0x7; // Strip the A|M|P flags from the size.
Run Code Online (Sandbox Code Playgroud)
请记住,这chunk_size是块的内部大小,而不是可用用户大小(即 32)。
最后但并非最不重要的一点是:指针的正确格式说明符printfis %p、 not %x(并且它还应该已经包含前导0x):
printf("%p\n", a);
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
1020 次 |
| 最近记录: |