(C) как распределитель кучи обрабатывает 4-байтовый заголовок блока, возвращая только адреса, кратные 8? - PullRequest
1 голос
/ 01 июня 2010

Это, кажется, не имеет смысла, если только мы не проигнорируем потенциальное избыточное пространство в начале сегмента, а затем не выделим первый выделенный фрагмент в первом кратном 8 (с соответствующим адресом в этом первом заголовке. -4). Это оставило бы много байтов до этого неиспользованным. Это то, что обычно делается?

редактирование: спасибо paxdiablo за подробное объяснение ниже. это все имеет смысл для 16-байтовых заголовков. Тем не менее, я работаю с 4-байтовым заголовком, который выглядит примерно так:

struct mhdr {
    int size;  // size of this block
} tMallocHdr;

Теперь, если моя куча начинается с адреса, кратного 8, и любой адрес, возвращаемый malloc, должен быть кратен 8, и мне нужно использовать 4-байтовые заголовки, я, похоже, вынужден тратить первые 4 байта моей кучи. например:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
              ^
              (heap starts)

Если куча начинается с адреса выше по адресу 8, используя схему адресации в этом примере, первый возвращаемый адрес, который я мог бы вернуть пользователю после вызова malloc, будет 16; мне нужно 4 байта заголовка, и первый адрес, кратный 8, который позволяет 4 байта заголовка, равен 16 (заголовок начинается с 12). Это означает, что я потратил первые 4 байта моей внутренней памяти кучи для выравнивания (8-11).

Это приемлемая жертва, или я думаю об этом неправильно?

Ответы [ 3 ]

6 голосов
/ 01 июня 2010

Как правило, - это потерянное пространство в заголовке блока выделенной области. Многие реализации, которые я видел, используют 16-байтовый (я использую здесь классическое определение байта, один из восьми битов, а не определение ISO C), непосредственно перед возвращением адреса из malloc, и добавляем выделенная область до 16 байтов в конце.

Это значительно упрощает алгоритмы, используемые для выделения, а также гарантирует, что возвращаемые адреса памяти будут выровнены соответствующим образом для архитектуры.

Но имейте в виду, что это деталь реализации, а не особенность стандарта C. Если нет требований к выравниванию, а выделение памяти ограничено 255 байтами, вполне вероятно, что будет потрачен только один байт (хотя и за счет более сложного алгоритма).


Вполне вероятно, что у вас могло бы быть встроенное приложение, которое когда-либо выделяло бы только 256-байтовые чанки, и в этом случае вы могли бы иметь простой распределитель на основе растровых изображений (с растровым изображением, хранящимся в другом месте, а не в соответствии с выделенными блоками памяти). ), тратя впустую только один бит на чанк (мы делали это раньше в средах с нехваткой памяти).

Или, возможно, у вас есть распределитель в большом адресном пространстве и памяти, который дает вам 4G независимо от того, что вы просите. Тогда есть нет потерь для заголовка, но, вероятно, достаточно для заполнения: -)

Или, может быть, вы получаете кусок от арены определенного размера (1-64 байта от арены A, 65-128 от арены B и так далее). Это означает, что заголовок не требуется, но все же допускается переменный размер (до максимума) и существенно меньше потерь, чем в решении 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;

И, хотя это заполнение является технически бесполезным пространством, оно становится важным, только если оно составляет значительную долю всей арены. Если вы выделяете 4 Кбайт памяти, то четыре байта потерь составляют всего лишь одну тысячную от общего размера. На самом деле, вы, как пользователь, теряете, вероятно, целые 16 байтов заголовка, так как это память, которую вы не можете использовать, поэтому она составляет около 0,39% (16 / (4096 + 16)).

Вот почему связанный список символов в malloc - очень плохая идея - вы склонны использовать для каждого отдельного символа:

  • 16 байтов заголовка.
  • 1 байт для символа (при условии 8-битных символов).
  • 15 байтов заполнения.

Это изменит ваш показатель 0,39% на 96,9% (31 / (31 + 1)).


И, в ответ на ваш дальнейший вопрос:

Это приемлемая жертва, или я думаю об этом неправильно?

Я бы сказал, да, это приемлемо. Как правило, вы выделяете большие куски памяти, где четыре байта не будут иметь значения в общей схеме вещей. Как я уже говорил ранее, это не очень хорошее решение, если вы выделяете много мелочей, но это не общий случай использования malloc.

1 голос
/ 01 июня 2010

Вы, вероятно, обнаружите, что если возвращаемые блоки всегда выровнены по 8-байтовой границе, реализация использует 8-байтовый блок для хранения любой управляющей информации. Я считаю, что реализация в K & R (как первом, так и втором изданиях) делает это. Действительно, он использует указатель и размер, поэтому он имеет 8 байтов управляющей информации. И если он использует 8-байтовые выровненные блоки, то реального наказания за это нет. (На 64-битных машинах наиболее строгое выравнивание может быть 16-байтовым - например, на SPARC - и поэтому издержки блока несколько больше, но в основном по той же причине.)

1 голос
/ 01 июня 2010

Некоторые распределители не имеют заголовков для выделенных блоков. Некоторые исключают заголовки для небольших блоков. Нет необходимости использовать заголовки для выделенных блоков, кроме как для безопасности. Это может быть достигнуто другими способами, например, с помощью административных данных, полностью физически отделенных от выделенной памяти.

Даже типизированные распределители памяти могут исключать заголовки. См. Например BIBOP .

...