Внутреннее представление чанка в glib c имеет следующую структуру:
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), и всегда должно быть 16 дополнительных байтов для mchunk_prev_size
и mchunk_size
(по 8 байтов каждый). Кроме того, чанки всегда выровнены по границам 16 байтов (например, их шестнадцатеричный адрес всегда заканчивается на 0
).
Итак, теперь вы, вероятно, можете угадать ответ на свой первый вопрос:
[...] наименьший блок, который выделяет c, равен 32 или, скорее, 4 * sizeof(void*)
. Однако, когда я выделяю 32 байта, следующий фрагмент находится на расстоянии 48 байтов, а не 64, почему?
Ну, да, наименьший размер фрагмента равен 32, но приращение на самом деле 16. Таким образом, вы можете иметь любой размер, кратный 16 и больше или равный 32. Если вы запрашиваете размер от 17 до 32, вы получите фрагмент размером 48 байтов (32 из которых можно использовать для пользовательских данных). . Кроме того, минимальный размер выделения malloc
не имеет ничего общего с sizeof(void *)
, на самом деле он больше связан с sizeof(size_t)
(как также указано в вашей ссылке).
Состояние кучи после распределения в вашем примере следующие:
+-----------+ 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 |
| |
+-----------+
Теперь, переходя ко второму вопросу:
И если mallo c выделил 32 и 48 байтов, почему он печатает 33 и 49 соответственно?
Поскольку размер каждого фрагмента должен быть кратен 16 байтам, младшие 4 бита размера (один шестнадцатеричный di git) останутся неиспользованными. malloc
экономит место и использует его для хранения дополнительной информации о фрагменте. Последние 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
: предыдущий фрагмент уже используется (т.е. не бесплатно).
Вы можете найти более подробное объяснение справа вверху в исходном коде mallo c .
Поскольку все ваши фрагменты все еще используются, в поле размера вы видите size | PREV_IN_USE
. Поскольку «предыдущий использованный» (P
) является наименее значимым битом, это приводит к увеличению значения размера на 1, поэтому вы видите 33
вместо 32
, например.
Некоторые дополнительные примечания:
Не приводите возвращаемое значение 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);