GLIB C mallo c бухгалтерия внедрения - PullRequest
1 голос
/ 30 мая 2020

Я пытаюсь понять, как именно mallo c glib c ведет учет на моей 64-битной машине.

Согласно документации, он хранит фактический размер (mallo c значение плюс байты учета) прямо перед блоком. Итак, я взял следующий код из здесь :

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 байта, это имеет смысл, поскольку наименьший блок, который выделяет mallo c, равен 32 или, скорее, 4 * sizeof (void *) . Однако, когда я выделяю 32 байта, следующий блок находится на расстоянии 48 байтов, а не 64, почему?

И если mallo c выделил 32 и 48 байтов, почему он печатает 33 и 49 соответственно?

1 Ответ

3 голосов
/ 30 мая 2020

Внутреннее представление чанка в 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);
    
...