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


85

У мене фон C #. Я дуже новачок у мові низького рівня, як C.

У C # structпам'ять розміщена компілятором за замовчуванням. Компілятор може переупорядковувати поля даних або неявно розміщувати додаткові біти між полями. Отже, мені довелося вказати якийсь спеціальний атрибут, щоб замінити цю поведінку для точного розміщення.

AFAIK, C не впорядковує та не вирівнює макет пам'яті structза замовчуванням. Однак я чув, що є невеликий виняток, який дуже важко знайти.

Яка поведінка макета пам'яті C? Що слід переупорядковувати / вирівнювати, а ні?

Відповіді:


110

У C компілятору дозволено диктувати деяке вирівнювання для кожного примітивного типу. Зазвичай вирівнювання - це розмір типу. Але це повністю залежить від реалізації.

Введені байти для заповнення, щоб кожен об'єкт був правильно вирівняний. Переупорядкування не дозволяється.

Можливо, кожен віддалено сучасний компілятор реалізує програму, #pragma packяка дозволяє контролювати відступи та залишає за програмістом відповідність ABI. (Однак це суворо нестандартно.)

З C99 §6.7.2.1:

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

13 У об'єкті структури члени небітового поля та одиниці, в яких знаходяться бітові поля, мають адреси, що збільшуються в порядку, в якому вони оголошені. Вказівник на об'єкт структури, відповідним чином перетворений, вказує на його початковий член (або якщо цей член є бітовим полем, то на одиницю, в якій він знаходиться), і навпаки. В об’єкті структури може бути неназване заповнення, але не на його початку.


1
Деякі компілятори (тобто GCC) реалізують той самий ефект, що #pragma packі з більш точним контролем над семантикою.
Кріс Луц,

21
Я здивований, побачивши голос проти. Хтось може вказати на помилку?
Potatoswatter

2
C11 також має _Alignas.
idmean

117

Це залежить від реалізації, але на практиці правило (за відсутності #pragma packабо подібного):

  • Члени структури зберігаються в порядку, в якому вони оголошені. (Це вимагається стандартом C99, як уже згадувалося тут раніше.)
  • За необхідності додається відступ перед кожним елементом структури для забезпечення правильного вирівнювання.
  • Кожен примітивний тип T вимагає вирівнювання sizeof(T)байтів.

Отже, враховуючи таку структуру:

struct ST
{
   char ch1;
   short s;
   char ch2;
   long long ll;
   int i;
};
  • ch1 має зміщення 0
  • для вирівнювання вставлений байт заповнення ...
  • s зі зміщенням 2
  • ch2 має зміщення 4, відразу після s
  • Для вирівнювання вставлено 3 байти для заповнення ...
  • ll на зміщення 8
  • i знаходиться на зміщенні 16, відразу після ll
  • В кінці додаються 4 байти для заповнення, так що загальна структура кратна 8 байтам. Я перевірив це на 64-бітній системі: 32-бітні системи можуть дозволити структурам мати 4-байтове вирівнювання.

Так sizeof(ST)само 24.

Його можна зменшити до 16 байт, переставивши елементи, щоб уникнути заповнення:

struct ST
{
   long long ll; // @ 0
   int i;        // @ 8
   short s;      // @ 12
   char ch1;     // @ 14
   char ch2;     // @ 15
} ST;

3
Якщо потрібно, додавання відступів додається до ... Більше, як після. Краще додати останнього charучасника до свого прикладу.
Дедулікатор

9
Примітивний тип не обов'язково вимагає вирівнювання sizeof(T)байтів. Наприклад, doubleзагальна 32-розрядна архітектура має 8 байт, але часто потрібно лише 4-байтове вирівнювання . Крім того, прокладка в кінці структури лише підкладає до вирівнювання найширшого елемента структури. Наприклад, структура з 3 змінних char не може мати відступів.
Метт

1
@ dan04, чи було б гарною практикою розміщувати структури у порядку зменшення розміру (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 повертається до макета за замовчуванням.

Дякую за турботу. Я змінив запитання, як ви вказували.
eonil
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.