GLIBC malloc 实现记账
GLIBC malloc implementation bookkeeping
我想了解 glibc 的 malloc 在我的 64 位机器上究竟是如何进行簿记的。
根据文档,它在块之前存储实际大小(malloc 值加上簿记字节)。所以我从 here:
中获取了以下代码
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]);
产生:
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
所以你可以看到前三个地址相隔 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;
};
除了 mchunk_prev_size
和 mchunk_size
之外的每个字段仅在块空闲时才填充。这两个字段就在用户可用缓冲区之前。 mchunk_prev_size
字段保存前一个块的大小(如果它是空闲的),而 mchunk_size
字段保存块的实际大小(比请求的大小至少多 16 个字节)。
最小分配大小为 16(对于较小大小的请求只是四舍五入为 16),mchunk_prev_size
和 mchunk_size
总是需要 16 个额外字节(每个 8 个字节) .此外,块始终与 16 字节边界对齐(例如,它们的十六进制地址始终以 0
结尾)。
现在您大概可以猜出第一个问题的答案了:
[...] the smallest chunk malloc allocates is 32 or rather 4 * sizeof(void*)
. However when I allocate 32 bytes the next chunk is 48 bytes away and not 64, why is that?
嗯,是的,最小 块大小是 32,但增量实际上是 16。因此,您可以有任何大小是 16 的倍数并且大于或等于32. 如果请求大小介于 17 和 32 之间,您将获得 48 字节的块(其中 32 字节可用于用户数据)。此外,最小 malloc
分配大小与 sizeof(void *)
没有太大关系,它实际上与 sizeof(size_t)
更相关(正如您的 link 也指出的那样)。
您的示例中分配后堆的状态如下:
+-----------+ 0xfca04290
| prev size |
|-----------|
| size |
a --> |-----------| 0xfca042a0
| user data |
| |
+-----------+ 0xfca042b8
| 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 |
| |
+-----------+
现在,进入第二个问题:
And if malloc has allocated 32 and 48 bytes why is it printing 33 and 49 respectively?
由于每个块的大小必须是 16 字节的倍数,因此该大小的最低 4 位(一个十六进制数字)将保持未使用状态。 malloc
保存 space 并使用它们来存储有关块的附加信息。最后 3 位实际上是 malloc
内部使用的标志。这在源代码注释中也有解释:
chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Size of previous chunk, if unallocated (P clear) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Size of chunk, in bytes |A|M|P| <== flags
mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| User data starts here... |
那些标志位 A|M|P
是:
A
: 非主竞技场区块。
M
:块是 mmap
ed.
P
:前一个块正在使用中(即不可用)。
你可以在上面找到更详尽的解释权in the malloc source code。
由于您的所有块仍在使用中,因此在大小字段中您会看到 size | PREV_IN_USE
。由于“previous in use”(P
)是最低有效位,这具有将大小值递增 1 的效果,因此您看到的是 33
而不是 32
例如。
一些补充说明:
Don't cast the return value of malloc
.
如果你想检查一个块的大小,你应该使用 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.
请记住,这个 chunk_size
是块的内部大小,而不是可用的用户大小(即 32)。
最后但同样重要的是:printf
的指针的正确格式说明符是 %p
,而不是 %x
(并且它还应该已经包括前导 0x
):
printf("%p\n", a);
我想了解 glibc 的 malloc 在我的 64 位机器上究竟是如何进行簿记的。
根据文档,它在块之前存储实际大小(malloc 值加上簿记字节)。所以我从 here:
中获取了以下代码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]);
产生:
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
所以你可以看到前三个地址相隔 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;
};
除了 mchunk_prev_size
和 mchunk_size
之外的每个字段仅在块空闲时才填充。这两个字段就在用户可用缓冲区之前。 mchunk_prev_size
字段保存前一个块的大小(如果它是空闲的),而 mchunk_size
字段保存块的实际大小(比请求的大小至少多 16 个字节)。
最小分配大小为 16(对于较小大小的请求只是四舍五入为 16),mchunk_prev_size
和 mchunk_size
总是需要 16 个额外字节(每个 8 个字节) .此外,块始终与 16 字节边界对齐(例如,它们的十六进制地址始终以 0
结尾)。
现在您大概可以猜出第一个问题的答案了:
[...] the smallest chunk malloc allocates is 32 or rather
4 * sizeof(void*)
. However when I allocate 32 bytes the next chunk is 48 bytes away and not 64, why is that?
嗯,是的,最小 块大小是 32,但增量实际上是 16。因此,您可以有任何大小是 16 的倍数并且大于或等于32. 如果请求大小介于 17 和 32 之间,您将获得 48 字节的块(其中 32 字节可用于用户数据)。此外,最小 malloc
分配大小与 sizeof(void *)
没有太大关系,它实际上与 sizeof(size_t)
更相关(正如您的 link 也指出的那样)。
您的示例中分配后堆的状态如下:
+-----------+ 0xfca04290
| prev size |
|-----------|
| size |
a --> |-----------| 0xfca042a0
| user data |
| |
+-----------+ 0xfca042b8
| 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 |
| |
+-----------+
现在,进入第二个问题:
And if malloc has allocated 32 and 48 bytes why is it printing 33 and 49 respectively?
由于每个块的大小必须是 16 字节的倍数,因此该大小的最低 4 位(一个十六进制数字)将保持未使用状态。 malloc
保存 space 并使用它们来存储有关块的附加信息。最后 3 位实际上是 malloc
内部使用的标志。这在源代码注释中也有解释:
chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Size of previous chunk, if unallocated (P clear) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Size of chunk, in bytes |A|M|P| <== flags
mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| User data starts here... |
那些标志位 A|M|P
是:
A
: 非主竞技场区块。M
:块是mmap
ed.P
:前一个块正在使用中(即不可用)。
你可以在上面找到更详尽的解释权in the malloc source code。
由于您的所有块仍在使用中,因此在大小字段中您会看到 size | PREV_IN_USE
。由于“previous in use”(P
)是最低有效位,这具有将大小值递增 1 的效果,因此您看到的是 33
而不是 32
例如。
一些补充说明:
Don't cast the return value of
malloc
.如果你想检查一个块的大小,你应该使用
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.
请记住,这个
chunk_size
是块的内部大小,而不是可用的用户大小(即 32)。最后但同样重要的是:
printf
的指针的正确格式说明符是%p
,而不是%x
(并且它还应该已经包括前导0x
):printf("%p\n", a);