Структура памяти в C

86

У меня опыт работы на C #. Я новичок в языке низкого уровня, таком как C.

В C # structпамять по умолчанию распределяется компилятором. Компилятор может неявно изменять порядок полей данных или вставлять дополнительные биты между полями. Итак, мне пришлось указать какой-то специальный атрибут, чтобы переопределить это поведение для точного макета.

AFAIK, C structпо умолчанию не меняет порядок и не выравнивает структуру памяти . Однако я слышал, что есть небольшое исключение, которое очень трудно найти.

Каково поведение разметки памяти C? Что нужно переупорядочивать / выравнивать, а что нет?

эонил
источник

Ответы:

111

В C компилятору разрешено диктовать некоторое выравнивание для каждого примитивного типа. Обычно выравнивание - это размер шрифта. Но это полностью зависит от реализации.

Добавлены байты заполнения, чтобы каждый объект был правильно выровнен. Повторный заказ не допускается.

Возможно, каждый удаленно современный компилятор реализует #pragma pack который позволяет контролировать заполнение и оставляет программисту соблюдение ABI. (Однако это строго нестандартно.)

Из C99 §6.7.2.1:

12 Каждый член структуры или объединенного объекта, не являющийся битовым полем, выравнивается способом, определяемым реализацией, соответствующим его типу.

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

Potatoswatter
источник
1
Некоторые компиляторы (например, GCC) реализуют тот же эффект, #pragma packно с более детальным контролем над семантикой.
Крис Лутц,
21
Я удивлен, увидев отрицательный голос. Кто-нибудь может указать на ошибку?
Potatoswatter
2
C11 также имеет _Alignas.
idmean 03
117

Это #pragma packзависит от реализации, но на практике правило (при отсутствии или подобном) таково:

  • Члены структуры хранятся в том порядке, в котором они объявлены. (Это требуется стандартом C99, как упоминалось здесь ранее.)
  • При необходимости перед каждым членом структуры добавляется отступ, чтобы обеспечить правильное выравнивание.
  • Каждый примитивный тип T требует выравнивания sizeof(T)байтов.

Итак, учитывая следующую структуру:

  • ch1 находится по смещению 0
  • вставляется байт заполнения для выравнивания ...
  • s по смещению 2
  • ch2 находится по смещению 4, сразу после s
  • Вставляются 3 байта заполнения для выравнивания ...
  • ll по смещению 8
  • i находится по смещению 16, сразу после ll
  • В конце добавляются 4 байта заполнения, так что общая структура кратна 8 байтам. Я проверил это в 64-битной системе: 32-битные системы могут позволить структурам иметь 4-байтовое выравнивание.

Так sizeof(ST)что 24.

Его можно уменьшить до 16 байт, переставив элементы, чтобы избежать заполнения:

dan04
источник
3
Если необходимо, перед ... больше похоже после. Лучше всего добавить последнего charчлена в свой пример.
Дедупликатор
9
Примитивный тип не обязательно требует выравнивания sizeof(T)байтов. Например, в doubleобычных 32-битных архитектурах длина 8 байтов, но часто требуется только 4-байтовое выравнивание . Кроме того, заполнение в конце структуры дополняет только выравнивание самого широкого элемента структуры. Например, структура из 3-х символьных переменных может не иметь заполнения.
Мэтт
1
@ dan04, было бы неплохо расположить структуры в порядке убывания sizeof (T). Будет ли это делать какие-то недостатки?
RohitMat
11

Вы можете начать с чтения статьи в Википедии о выравнивании структуры данных в чтобы лучше понять выравнивание данных.

Из статьи в Википедии :

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

Из 6.54.8 прагмы упаковки структуры документации GCC:

Для совместимости с компиляторами Microsoft Windows GCC поддерживает набор директив #pragma, которые изменяют максимальное выравнивание элементов структур (кроме битовых полей нулевой ширины), объединений и классов, определяемых впоследствии. Приведенное ниже значение n всегда должно быть малой степенью двойки и указывает новое выравнивание в байтах.

  1. #pragma pack(n) просто устанавливает новое выравнивание.
  2. #pragma pack() устанавливает выравнивание на то, которое действовало при запуске компиляции (см. также параметр командной строки -fpack-struct [=], см. Параметры генератора кода).
  3. #pragma pack(push[,n]) помещает текущую настройку выравнивания во внутренний стек, а затем при необходимости устанавливает новое выравнивание.
  4. #pragma pack(pop)восстанавливает настройку выравнивания до значения, сохраненного наверху внутреннего стека (и удаляет эту запись стека). Обратите внимание, что #pragma pack([n])это не влияет на этот внутренний стек; таким образом, возможно, что #pragma pack(push) за несколькими #pragma pack(n) экземплярами следуют и завершаются одним #pragma pack(pop).

Некоторые цели, например i386 и powerpc, поддерживают ms_struct, #pragmaкоторая устанавливает структуру, как задокументировано __attribute__ ((ms_struct)).

  1. #pragma ms_struct on включает макет для объявленных структур.
  2. #pragma ms_struct off отключает макет для объявленных структур.
  3. #pragma ms_struct reset возвращается к макету по умолчанию.
jschmier
источник
Спасибо за заботу. Я изменил вопрос, как вы руководствовались.
eonil 01