Структура набивки и упаковки

209

Рассматривать:

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

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

Размеры конструкций 12 и 8 соответственно.

Эти структуры дополнены или упакованы?

Когда происходит заполнение или упаковка?

Manu
источник
3
Читайте stackoverflow.com/questions/119123/…
Прасун Саурав
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
Первый член должен прийти первым? Я думал, что arragement полностью зависит от реализации, и на него нельзя положиться (даже от версии к версии).
allyourcode
4
+ allyourcode Стандарт гарантирует, что порядок членов будет сохранен и что первый член будет начинаться со смещения 0.
Мартынкунев
64

( Приведенные выше ответы объяснили причину довольно ясно, но, кажется, не совсем ясно о размере заполнения, поэтому я добавлю ответ в соответствии с тем, что я узнал из книги «Потерянное искусство структурной упаковки» , она эволюционировала не для ограничения C, а для также применимы к Go, Rust. )


Выравнивание памяти (для структуры)

Правила:

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

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

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

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

Правила:


  • Адрес 64-битной системной структуры начинается с (n * 16)байтов. ( Вы можете видеть в примере ниже, все напечатанные шестнадцатеричные адреса структур заканчиваются 0. )
    Причина : возможный самый большой отдельный элемент структуры составляет 16 байт ( long double).
  • (Обновление) Если структура содержит только элементcharas, ее адрес может начинаться с любого адреса.

Пустое место :

  • Пустое пространство между 2 структурами может использоваться неструктурными переменными, которые могут вписываться.
    Например, 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 Это очень короткая книга, посвященная в основном технологиям, которые позволят уменьшить объем памяти программы, для ее завершения требуется всего несколько дней.
Эрик Ван
1
@ValidusOculus Да, это означает, что 16 байтов выровнены.
Эрик Ван
44

Я знаю, что этот вопрос старый, и большинство ответов здесь очень хорошо объясняет заполнение, но, пытаясь понять его сам, я решил, что помогло «визуальное» представление о происходящем.

Процессор считывает память «кусками» определенного размера (слова). Скажем, процессорное слово имеет длину 8 байтов. Он будет смотреть на память как большой ряд 8-байтовых строительных блоков. Каждый раз, когда ему нужно получить некоторую информацию из памяти, он достигнет одного из этих блоков и получит его.

Выравнивание переменных

Как показано на рисунке выше, не имеет значения, где находится символ (длиной 1 байт), поскольку он будет находиться внутри одного из этих блоков, что потребует от процессора обработки только 1 слова.

Когда мы имеем дело с данными размером более одного байта, такими как 4-байтовое int или 8-байтовое двойное число, то, как они выровнены в памяти, влияет на то, сколько слов придется обрабатывать центральным процессором. Если 4-байтовые блоки выровнены таким образом, что они всегда соответствуют внутренней части блока (адрес памяти кратен 4), то нужно обработать только одно слово. В противном случае часть из 4 байтов может иметь часть себя в одном блоке и часть в другом, требуя, чтобы процессор обработал 2 слова для чтения этих данных.

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

Это касается 8-байтового текстового процессора, но концепция применима к другим размерам слов.

Заполнение работает, заполняя промежутки между этими данными, чтобы убедиться, что они выровнены с этими блоками, таким образом улучшая производительность при чтении памяти.

Однако, как указано в ответах других, иногда пространство имеет большее значение, чем сама производительность. Возможно, вы обрабатываете много данных на компьютере, на котором недостаточно ОЗУ (можно использовать пространство подкачки, но оно НАМНОГО медленнее). Вы можете расположить переменные в программе до тех пор, пока не будет выполнено наименьшее заполнение (как это было продемонстрировано в некоторых других ответах), но если этого недостаточно, вы можете явно отключить заполнение, что и является упаковкой .

IanC
источник
3
Это не объясняет упаковку структуры, но довольно хорошо иллюстрирует выравнивание слов процессора.
Дэвид Фёрстер
Вы нарисовали это в краске? :-)
Сиро Сантилли 郝海东 冠状 病 六四 事件 法轮功
1
@ CiroSantilli709 大 抓捕 六四 事件 法轮功, это было на gimp, но я думаю, я бы сэкономил немного времени на рисовании, ха-ха
IanC
1
Еще лучше, так как с открытым исходным кодом (Y)
Сиро Сантилли 法轮功 冠状 病 六四 事件 法轮功
21

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

Некоторые компиляторы предусматривают #pragmaподавление заполнения или его упаковку в n байтов. Некоторые предоставляют ключевые слова для этого. Обычно прагма, которая используется для изменения заполнения структуры, будет иметь следующий формат (зависит от компилятора):

#pragma pack(n)

Например, ARM предоставляет __packedключевое слово для подавления заполнения структуры. Просмотрите руководство по компилятору, чтобы узнать больше об этом.

Таким образом, упакованная структура - это структура без заполнения.

В основном будут использованы упакованные конструкции

  • сэкономить место

  • отформатировать структуру данных для передачи по сети, используя некоторый протокол (конечно, это не очень хорошая практика, потому что вам нужно
    иметь дело с порядком байтов)

user2083050
источник
5

Обивка и упаковка - это только два аспекта одного и того же:

  • упаковка или выравнивание - это размер, до которого округляется каждый элемент
  • отступ - это дополнительное пространство, добавляемое для выравнивания

В mystruct_Aпредположении, что выравнивание по умолчанию равно 4, каждый элемент выровнен по кратному 4 байтам. Поскольку размер charравен 1, заполнение для aи cсоставляет 4 - 1 = 3 байта, в то время как заполнение не требуется, для int bкоторого уже 4 байта. Это работает так же, как для mystruct_B.

Касабланка
источник
1

Упаковка структуры выполняется только тогда, когда вы явно указываете компилятору упаковать структуру. Обивка - это то, что вы видите. Ваша 32-битная система дополняет каждое поле выравниванием слов. Если бы вы сказали компилятору упаковать структуры, они бы составляли 6 и 5 байтов соответственно. Не делай этого, хотя. Он не переносим и заставляет компиляторы генерировать гораздо более медленный (а иногда даже ошибочный) код.

nmichaels
источник
1

Там нет ничего об этом! Кто хочет понять предмет должен сделать следующие,

  • Прочитайте Потерянное Искусство Упаковки Структуры, написанное Эриком С. Рэймондом
  • Посмотрите на пример кода Эрика
  • И последнее, но не менее важное: не забывайте следующее правило о заполнении, согласно которому структура выравнивается в соответствии с требованиями выравнивания самого большого типа.
ОСШ
источник
1

Правила для заполнения:

  1. Каждый член структуры должен быть по адресу, кратному его размеру. Заполнение вставляется между элементами или в конце структуры, чтобы убедиться, что это правило соблюдается. Это сделано для более простого и эффективного доступа к шине с помощью аппаратного обеспечения.
  2. Заполнение в конце структуры определяется на основе размера наибольшего члена структуры.

Почему Правило 2: Рассмотрим следующую структуру,

Структура 1

Если бы мы создали массив (из 2 структур) этой структуры, в конце не потребовалось бы заполнения:

Массив Struct1

Следовательно, размер структуры = 8 байт

Предположим, мы должны были создать другую структуру, как показано ниже:

Структура 2

Если бы нам нужно было создать массив этой структуры, было бы 2 варианта количества байтов заполнения, требуемого в конце.

О. Если мы добавим 3 байта в конце и выровняем его для int, а не для Long:

Массив Struct2 выровненный по int

B. Если мы добавим 7 байтов в конце и выровняем его для Long:

Массив Struct2 выровнен по Long

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

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

AlphaGoku
источник
-1

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

  1. Чтобы выровнять данные в памяти, один или несколько пустых байтов (адресов) вставляются (или остаются пустыми) между адресами памяти, которые выделяются для других элементов структуры во время выделения памяти. Эта концепция называется заполнением структуры.
  2. Архитектура компьютерного процессора такова, что он может считывать из памяти 1 слово (4 байта в 32-разрядном процессоре) за раз.
  3. Чтобы использовать это преимущество процессора, данные всегда выровнены как 4-байтовый пакет, что приводит к вставке пустых адресов между адресами других членов.
  4. Из-за этой концепции дополнения структуры в C размер структуры всегда не совпадает с тем, что мы думаем.
Манодж Ядав
источник
1
Зачем вам нужно ссылаться на одну и ту же статью 5 раз в ответе? Пожалуйста, оставьте только одну ссылку на пример. Кроме того, поскольку вы ссылаетесь на свою статью, вам необходимо раскрыть этот факт.
Artjom B.