Структура прокладки та упаковки


209

Поміркуйте:

struct mystruct_A
{
   char a;
   int b;
   char c;
} x;

struct mystruct_B
{
   int b;
   char a;
} y;

Розміри конструкцій - 12 і 8 відповідно.

Ці конструкції оббиті чи упаковані?

Коли відбувається прокладка або упаковка?



24
Втрачене мистецтво упаковки структури C - catb.org/esr/structure-packing
Паоло

paddingробить речі більшими. packingробить речі меншими. Зовсім різні.
smwikipedia

Відповіді:


264

Прокладки вирівнюють членів структури за "природними" межами адреси - скажімо, intчлени мали б зрушення, які знаходяться mod(4) == 0на 32-бітній платформі. Завантаження за замовчуванням увімкнено. Він вставляє такі "прогалини" у вашу першу структуру:

struct mystruct_A {
    char a;
    char gap_0[3]; /* inserted by compiler: for alignment of b */
    int b;
    char c;
    char gap_1[3]; /* -"-: for alignment of the whole struct in an array */
} x;

З іншого боку, упаковка забороняє компілятору робити набивки - це потрібно чітко запитувати - в GCC це __attribute__((__packed__))так, тож наступне:

struct __attribute__((__packed__)) mystruct_A {
    char a;
    int b;
    char c;
};

створює структуру розміру 6для 32-бітної архітектури.

Однак зауваження - нестандартний доступ до пам’яті повільніший для архітектур, які дозволяють це (наприклад, x86 та amd64), і явно заборонено в архітектурах суворого вирівнювання, таких як SPARC.


2
Цікаво: чи заборона несогласованої пам'яті на іскрі означає, що вона не може мати справу зі звичайними байтовими масивами? Упаковка структур, як я знаю, використовується в основному при передачі (тобто в мережі) даних, коли вам потрібно передавати байтовий масив в структуру, і переконайтеся, що масив підходить до полів структури. Якщо іскра не може цього зробити, як ті, хто працює взагалі ?!
Привіт-Ангел

14
Саме тому, якщо ви подивитеся на макети заголовків IP, UDP та TCP, ви побачите, що всі цілі поля вирівняні.
Микола Фетисов

17
У "Втраченому мистецтві упаковки з структури C" пояснюються петимізація набивання та упаковки - catb.org/esr/structure-packing
Rob11311

3
Чи повинен перший член прийти? Я вважав, що аргументація повністю залежить від реалізації, і на неї не можна покластися (навіть від версії до версії).
allyourcode

4
+ allyourcode Стандарт гарантує, що порядок членів буде збережений і що перший член розпочнеться з 0 зміщення.
мартінкунев

64

( Вищенаведені відповіді пояснили причину досить чітко, але здається не зовсім зрозумілою щодо розміру набивки, тому я додам відповідь відповідно до того, що я дізнався із програшеного пакування втраченого мистецтва , воно розвивалося не обмежуючись C, але також застосовні до Go, Rust. )


Вирівнювання пам'яті (для структури)

Правила:

  • Перед кожним окремим учасником буде прокладка, щоб почати її за адресою, що ділиться на її розмір.
    наприклад, у 64-бітній системі intслід починати з адреси, розділеної на 4, і longна 8, shortна 2.
  • charі char[]вони особливі, можуть бути будь-якою адресою пам'яті, тому перед ними не потрібно прокладки.
  • Бо struct, крім необхідності вирівнювання кожного окремого члена, розмір усієї структури сам буде вирівнюватися до розміру, що ділиться на розмір найбільшого окремого елемента, шляхом прокладки в кінці.
    наприклад, якщо найбільший член longstruk тоді ділиться на 8, intпотім на 4, shortпотім на 2.

Порядок учасника:

  • Порядок члена може вплинути на фактичний розмір структури, тому врахуйте це. наприклад, stu_cі stu_dз прикладу, наведеного нижче, мають однакові члени, але в іншому порядку і призводять до різного розміру для двох структур.

Адреса в пам'яті (для структури)

Правила:

  • 64-бітна системна
    адреса структури починається з (n * 16)байтів. ( Ви можете побачити в прикладі нижче, всі надруковані шістнадцяткові адреси структур закінчуються 0. )
    Причина : можливий найбільший окремий член структури - 16 байт ( long double).
  • (Оновлення) Якщо структура містить лишеcharчлен, її адреса може починатися з будь-якої адреси.

Порожній пробіл :

  • Порожній простір між двома структурами може використовуватися неструктурними змінними, які можуть вміститися.
    Наприклад, test_struct_address()внизу, змінна xзнаходиться між сусідніми структурами gта h.
    Незалежно від того x, задекларовано, hадреса не зміниться, xпросто повторно використайте порожнє місце, яке gвитратили даремно.
    Аналогічний випадок для y.

Приклад

( для 64-бітної системи )

memory_align.c :

/**
 * Memory align & padding - for struct.
 * compile: gcc memory_align.c
 * execute: ./a.out
 */ 
#include <stdio.h>

// size is 8, 4 + 1, then round to multiple of 4 (int's size),
struct stu_a {
    int i;
    char c;
};

// size is 16, 8 + 1, then round to multiple of 8 (long's size),
struct stu_b {
    long l;
    char c;
};

// size is 24, l need padding by 4 before it, then round to multiple of 8 (long's size),
struct stu_c {
    int i;
    long l;
    char c;
};

// size is 16, 8 + 4 + 1, then round to multiple of 8 (long's size),
struct stu_d {
    long l;
    int i;
    char c;
};

// size is 16, 8 + 4 + 1, then round to multiple of 8 (double's size),
struct stu_e {
    double d;
    int i;
    char c;
};

// size is 24, d need align to 8, then round to multiple of 8 (double's size),
struct stu_f {
    int i;
    double d;
    char c;
};

// size is 4,
struct stu_g {
    int i;
};

// size is 8,
struct stu_h {
    long l;
};

// test - padding within a single struct,
int test_struct_padding() {
    printf("%s: %ld\n", "stu_a", sizeof(struct stu_a));
    printf("%s: %ld\n", "stu_b", sizeof(struct stu_b));
    printf("%s: %ld\n", "stu_c", sizeof(struct stu_c));
    printf("%s: %ld\n", "stu_d", sizeof(struct stu_d));
    printf("%s: %ld\n", "stu_e", sizeof(struct stu_e));
    printf("%s: %ld\n", "stu_f", sizeof(struct stu_f));

    printf("%s: %ld\n", "stu_g", sizeof(struct stu_g));
    printf("%s: %ld\n", "stu_h", sizeof(struct stu_h));

    return 0;
}

// test - address of struct,
int test_struct_address() {
    printf("%s: %ld\n", "stu_g", sizeof(struct stu_g));
    printf("%s: %ld\n", "stu_h", sizeof(struct stu_h));
    printf("%s: %ld\n", "stu_f", sizeof(struct stu_f));

    struct stu_g g;
    struct stu_h h;
    struct stu_f f1;
    struct stu_f f2;
    int x = 1;
    long y = 1;

    printf("address of %s: %p\n", "g", &g);
    printf("address of %s: %p\n", "h", &h);
    printf("address of %s: %p\n", "f1", &f1);
    printf("address of %s: %p\n", "f2", &f2);
    printf("address of %s: %p\n", "x", &x);
    printf("address of %s: %p\n", "y", &y);

    // g is only 4 bytes itself, but distance to next struct is 16 bytes(on 64 bit system) or 8 bytes(on 32 bit system),
    printf("space between %s and %s: %ld\n", "g", "h", (long)(&h) - (long)(&g));

    // h is only 8 bytes itself, but distance to next struct is 16 bytes(on 64 bit system) or 8 bytes(on 32 bit system),
    printf("space between %s and %s: %ld\n", "h", "f1", (long)(&f1) - (long)(&h));

    // f1 is only 24 bytes itself, but distance to next struct is 32 bytes(on 64 bit system) or 24 bytes(on 32 bit system),
    printf("space between %s and %s: %ld\n", "f1", "f2", (long)(&f2) - (long)(&f1));

    // x is not a struct, and it reuse those empty space between struts, which exists due to padding, e.g between g & h,
    printf("space between %s and %s: %ld\n", "x", "f2", (long)(&x) - (long)(&f2));
    printf("space between %s and %s: %ld\n", "g", "x", (long)(&x) - (long)(&g));

    // y is not a struct, and it reuse those empty space between struts, which exists due to padding, e.g between h & f1,
    printf("space between %s and %s: %ld\n", "x", "y", (long)(&y) - (long)(&x));
    printf("space between %s and %s: %ld\n", "h", "y", (long)(&y) - (long)(&h));

    return 0;
}

int main(int argc, char * argv[]) {
    test_struct_padding();
    // test_struct_address();

    return 0;
}

Результат виконання - test_struct_padding():

stu_a: 8
stu_b: 16
stu_c: 24
stu_d: 16
stu_e: 16
stu_f: 24
stu_g: 4
stu_h: 8

Результат виконання - test_struct_address():

stu_g: 4
stu_h: 8
stu_f: 24
address of g: 0x7fffd63a95d0  // struct variable - address dividable by 16,
address of h: 0x7fffd63a95e0  // struct variable - address dividable by 16,
address of f1: 0x7fffd63a95f0 // struct variable - address dividable by 16,
address of f2: 0x7fffd63a9610 // struct variable - address dividable by 16,
address of x: 0x7fffd63a95dc  // non-struct variable - resides within the empty space between struct variable g & h.
address of y: 0x7fffd63a95e8  // non-struct variable - resides within the empty space between struct variable h & f1.
space between g and h: 16
space between h and f1: 16
space between f1 and f2: 32
space between x and f2: -52
space between g and x: 12
space between x and y: 12
space between h and y: 8

Таким чином, початок адреси для кожної змінної дорівнює g: d0 x: dc h: e0 y: e8

введіть тут опис зображення


4
"Правила" насправді дали це дуже зрозуміло, я не міг ніде знайти прямого правила. Дякую.
Первес Алам

2
@PervezAlam У книзі <The Lost Art of C Structure Packing>досить добре пояснюються правила, навіть думав, що це трохи довше, ніж ця відповідь. Книга доступна у вільному доступі в Інтернеті: catb.org/esr/structure-packing
Ерік Ван

Я спробую це, до речі, це обмежується упаковкою Структура? Просто цікаво, як мені сподобалось пояснення в книзі.
Первес Алам

1
@PervezAlam Це дуже коротка книга, в основному зосереджена на технології, яка б знизила слід пам’яті програми c, для читання потрібно лише максимум кілька днів.
Ерік Ван

1
@ValidusOculus Так, це означає, що вирівнюється 16 байт.
Ерік Ван

44

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

Процесор зчитує пам'ять «шматками» певного розміру (слово). Скажімо, слово процесора - 8 байт. Він буде розглядати пам'ять як великий ряд з 8 байт будівельних блоків. Щоразу, коли йому потрібно отримати якусь інформацію з пам'яті, вона дістанеться до одного з цих блоків і отримає її.

Вирівнювання змінних

Як здається на зображенні вище, не має значення, де знаходиться Char (довжиною 1 байт), оскільки він буде знаходитися всередині одного з цих блоків, вимагаючи, щоб ЦП обробляв лише 1 слово.

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

Те саме стосується 8-байтового подвійного, за винятком випадків, коли він повинен знаходитись у адресі пам'яті, кратній 8, щоб гарантувати, що він завжди буде знаходитись у блоці.

Це враховує 8-байтовий текстовий процесор, але ця концепція стосується інших розмірів слів.

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

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


3
Це не пояснює упаковку структури, але це досить добре ілюструє вирівнювання слів CPU.
Девід Фоерстер

Ви намалювали це фарбою? :-)
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

1
@ CiroSantilli709 大 抓捕 六四 事件 法轮功, це було на gimp, але я думаю, я би врятував деякий час, роблячи це на фарбі, хоча ха-ха
IanC

1
Ще краще, оскільки з відкритим кодом (Y)
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

21

Упаковка структури пригнічує прокладку структури, прокладки, які використовуються, коли найбільше значення має вирівнювання, упаковка, яка використовується, коли найбільше значення має простір.

Деякі компілятори пропонують #pragmaпридушити прокладку або зробити її упакованою до n кількості байтів. Деякі надають ключові слова для цього. Зазвичай прагма, яка використовується для зміни структури накладки, буде у форматі нижче (залежить від компілятора):

#pragma pack(n)

Наприклад, ARM надає __packedключове слово для придушення набивання структури. Перегляньте посібник зі збирача, щоб дізнатися більше про це.

Так упакована структура - це структура без прокладки.

В основному будуть використовуватися упаковані конструкції

  • економити місце

  • відформатувати структуру даних для передачі по мережі за допомогою деякого протоколу (це, звичайно, не є хорошою практикою, тому що вам потрібно
    боротися з витримкою)


5

Прокладка та упаковка - це лише два аспекти одного і того ж:

  • упаковка або вирівнювання - це розмір, до якого округляється кожен член
  • padding - це додатковий простір, який відповідає суміщенню

У mystruct_Aтакому випадку, якщо вважати вирівнювання за замовчуванням 4, кожен член вирівнюється на кратному 4 байти. Оскільки розмір charстановить 1, то прокладка для aта cстановить 4 - 1 = 3 байти, тоді як не потрібна додаткова обробка, для int bякої вже 4 байти. Це працює так само, як і для mystruct_B.


1

Упаковка структури виконується лише тоді, коли ви скажете своєму компілятору явно упакувати структуру. Закладка - це те, що ви бачите. Ваша 32-розрядна система додає кожне поле до вирівнювання слів. Якби ви сказали своєму компілятору упакувати структури, вони становитимуть 6 та 5 байт відповідно. Не робіть цього, хоча. Він не портативний, і змушує компілятори генерувати набагато повільніше (а іноді навіть баггі) код.


1

Про це немає жодних недоліків! Хто хоче зрозуміти тему, повинен зробити наступні,


1

Правила прокладки:

  1. Кожен член структури має бути за адресою, розділеною на його розмір. Прокладка вставляється між елементами або в кінці структури, щоб переконатися, що це правило виконується. Це робиться для полегшення та ефективнішого доступу автобуса за допомогою обладнання.
  2. Прокладка в кінці структури визначається виходячи з розміру найбільшого члена структури.

Чому правило 2: Розгляньте таку структуру,

Структура 1

Якщо ми маємо створити масив (з двох структур) цієї структури, то в кінці не буде потрібно ніяких прокладок:

Масив Struct1

Тому розмір struct = 8 байт

Припустимо, ми повинні створити іншу структуру, як показано нижче:

Структура 2

Якби ми створили масив цієї структури, є дві можливості кількості потрібних в кінці байтів.

A. Якщо ми додамо 3 байти в кінці і вирівняємо їх для int, а не Long:

Масив Struct2 вирівняний до int

B. Якщо ми додамо 7 байт в кінці і вирівняємо його для Long:

Масив Struct2 вирівняний по Long

Початкова адреса другого масиву кратна 8 (тобто 24). Розмір структури = 24 байти

Отже, вирівнюючи початкову адресу наступного масиву структури до кратного найбільшого члена (тобто, якщо ми повинні створити масив цієї структури, перша адреса другого масиву повинна починатися з адреси, яка є кратною найбільшого члена структури. Ось це, 24 (3 * 8)), ми можемо обчислити кількість байтів підкладки, необхідних в кінці.


-1

Вирівнювання структури даних - це спосіб упорядкування та доступу до даних у пам'яті комп'ютера. Він складається з двох окремих, але пов’язаних із цим питань: вирівнювання даних та доповнення структури даних . Коли сучасний комп’ютер читає або записує на адресу пам'яті, він буде робити це в шматки розміру слова (наприклад, 4 байтові фрагменти в 32-бітній системі) або більше. Вирівнювання даних означає розміщення даних за адресою пам'яті, рівним деякому кратному розміру слова, що збільшує продуктивність системи завдяки тому, як процесор обробляє пам'ять. Для вирівнювання даних може знадобитися вставити кілька безглуздих байтів між кінцем останньої структури даних та початком наступної, що є вставкою структури даних.

  1. Для вирівнювання даних у пам'яті один або декілька порожніх байтів (адрес) вставляються (або залишаються порожніми) між адресами пам'яті, які виділяються для інших членів структури під час розподілу пам'яті. Ця концепція називається накладкою структури.
  2. Архітектура комп'ютерного процесора така, що він може читати 1 слово (4 байти в 32-бітовому процесорі) одночасно з пам'яті.
  3. Щоб скористатися цією перевагою процесора, дані завжди вирівнюються у вигляді 4-байтного пакету, що призводить до вставки порожніх адрес між адресою іншого учасника.
  4. Через цю концепцію прокладки структури в С розмір структури завжди не такий, як ми думаємо.

1
Чому у вашій відповіді потрібно 5 разів посилатися на ту саму статтю ? Будь ласка, зберігайте лише одне посилання на приклад. Крім того, оскільки ви посилаєтесь на свою статтю, вам слід розкрити цей факт.
Artjom B.
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.