Набивка и упаковка конструкции

Учитывать:

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

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

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

Эти конструкции набиты или набиты?

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


person Manu    schedule 29.11.2010    source источник
comment
Прочтите заголовок stackoverflow.com/questions/119123/   -  person Prasoon Saurav    schedule 29.11.2010
comment
Утраченное искусство упаковки структуры C - catb.org/esr/structure-packing   -  person Paolo    schedule 06.04.2016
comment
padding делает вещи больше. packing делает вещи меньше. Абсолютно другой.   -  person smwikipedia    schedule 25.06.2017
comment
@Paolo, эта ссылка Lost Art не показывает, что происходит, когда есть выравнивание указателя, и выше, где два целых числа могут быть один за другим.   -  person mLstudent33    schedule 10.07.2020
comment
Связанный, для C ++: stackoverflow.com/questions/44287060/   -  person Gabriel Staples    schedule 17.09.2020
comment
См. Также: stackoverflow.com/questions/3318410/pragma-pack-effect   -  person Gabriel Staples    schedule 18.09.2020


Ответы (11)


Заполнение выравнивает элементы структуры по «естественным» границам адреса, например 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.

person Nikolai Fetissov    schedule 29.11.2010
comment
Интересно: неужели запрет невыровненной памяти на искре означает, что он не может справиться с обычными байтовыми массивами? Упаковка структуры, как я знаю, в основном используется при передаче (например, в сети) данных, когда вам нужно преобразовать массив байтов в структуру и убедиться, что массив соответствует полям структуры. Если искра не может этого сделать, то как те вообще работают ?! - person Hi-Angel; 24.05.2014
comment
Именно поэтому, если вы посмотрите на макеты заголовков IP, UDP и TCP, вы увидите, что все целочисленные поля выровнены. - person Nikolai Fetissov; 24.05.2014
comment
Утраченное искусство упаковки структуры C объясняет оптимизацию заполнения и упаковки - catb.org/esr/structure- упаковка - person Rob11311; 29.06.2014
comment
/ * malloc () всегда предоставляет выровненную память / cptr = malloc (sizeof (int) + 1); / Увеличивает указатель на единицу, делая его смещенным / iptr = (int *) ++ cptr; / Разыменовать его как указатель int, вызывая невыровненный доступ * / * iptr = 42; Приведенный выше код даст ошибку шины. Но если я перестану упаковывать структуру, доступ к ее переменным ничего не даст? Почему? - person iDebD_gh; 17.02.2015
comment
Возможно, это просто спасло мне день. Интересно, какого черта моя структура, использующая fwrite, всегда выравнивается по 32-битным границам. Спасибо за подробный ответ и атрибут упаковано. - person JoshJ; 07.05.2015
comment
Первый член должен быть первым? Я думал, что расположение полностью зависит от реализации и на него нельзя положиться (даже от версии к версии). - person allyourcode; 30.07.2015
comment
@allyourcode Я считаю, что вы правы с точки зрения стандарта C, однако, когда вы упаковываете структуру, компилятор не будет нарушать порядок полей. Вся цель упаковки - дать программисту контроль над тем, как структура выглядит в памяти. - person Philip Couling; 06.04.2016
comment
хорошее чтение: geeksforgeeks.org/ - person parasrish; 24.04.2017
comment
+ allyourcode Стандарт гарантирует, что порядок членов будет сохранен и что первый член будет начинаться со смещения 0. - person martinkunev; 08.12.2017
comment
атрибут sizeof struct __ ((__ упаковано)) mystruct_A {char a; int b; char c; }; в моем компьютере дайте мне 9 - person Donghua Liu; 16.11.2018

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


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

Правила:

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

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

person user218867    schedule 01.07.2016
comment
Правила на самом деле прояснили это, я нигде не мог найти однозначного правила. Спасибо. - person Pervez Alam; 14.02.2017
comment
@PervezAlam Книга <The Lost Art of C Structure Packing> довольно хорошо объясняет правила, даже если подумать, что она немного длиннее, чем этот ответ. Книга доступна бесплатно в Интернете: catb.org/esr/structure-packing - person user218867; 14.02.2017
comment
Я попробую, кстати, это ограничивается упаковкой структуры? Просто любопытство, как мне понравилось объяснение в книге. - person Pervez Alam; 14.02.2017
comment
@PervezAlam Это очень короткая книга, в основном сфокусированная на технологии, которая уменьшит объем памяти, занимаемой программой c, занимает самое большее несколько дней, чтобы закончить чтение. - person user218867; 14.02.2017
comment
Согласно вашему первому пункту: означает ли это, что между структурами также добавляется заполнение. То есть сделать первый элемент следующей структуры делимым адресом? - person AlphaGoku; 06.02.2018
comment
@AkshayImmanuelD Да, каждый отдельный член подчиняется правилу 1, и начальный адрес первого члена также является начальным адресом самой структуры. - person user218867; 06.02.2018
comment
Что происходит с байтами заполнения между двумя структурами? Разве это не пустая трата памяти? - person AlphaGoku; 06.02.2018
comment
@AkshayImmanuelD Чтобы добиться выравнивания, необходимо заполнение. Хорошо спроектированная структура может содержать нужное количество членов в правильном порядке, а заполнение минимизировано, поэтому заполнение может не иметь большого значения для общей памяти. - person user218867; 06.02.2018
comment
Имеет смысл, почему некоторые стандарты кодирования говорят упорядочивать структуры от самых больших до самых маленьких элементов. - person AlphaGoku; 06.02.2018
comment
Из моего предыдущего комментария ›означает ли это, что между структурами также добавляется заполнение. То есть сделать первый элемент следующей структуры делимым адресом. Я где-то читал, что отступы никогда не могут быть между структурами. Заполнение в конце одной структуры обеспечивает выравнивание начала новой структуры. - person AlphaGoku; 12.02.2018
comment
@AkshayImmanuelD Пространство между двумя структурами на самом деле не принадлежит первой структуре, потому что другая переменная, не являющаяся структурой, может повторно использовать ее, когда это возможно. Проверьте обновленный ответ, в основном часть Address in memory - for struct. - person user218867; 12.02.2018
comment
Таким образом, компилятор может размещать статические переменные продолжительности в промежутках между структурами. - person AlphaGoku; 12.02.2018
comment
Таким образом, размер структуры всегда будет четным числом - person AlphaGoku; 24.08.2018
comment
Я думаю, что Address in memory - for struct не очень понятен, по крайней мере, для меня. Было бы полезно, если бы вы могли объяснить немного подробнее. - person Validus Oculus; 29.10.2018
comment
@ValidusOculus Я немного изменил ответ, пожалуйста, проверьте, сейчас он более разумный. - person user218867; 29.10.2018
comment
@EricWang Спасибо. Я только что понял, что это «возможная» часть вашего предложения. Максимальный возможный отдельный член структуры - 16 байт, поэтому я подумал, откуда, черт возьми, 16 байт в этой структуре взято из LOL. Спасибо! - person Validus Oculus; 29.10.2018
comment
@EricWang Что касается части «n * 16», что означает «n»? Вы имеете в виду, что все адреса структуры начинаются с адреса, выровненного по 16 байт? - person Validus Oculus; 29.10.2018
comment
@ValidusOculus Да, это означает выравнивание по 16 байтам. - person user218867; 29.10.2018
comment
@Eric Wang Спасибо! - person Validus Oculus; 30.10.2018
comment
Отличный ответ, но я не понимаю, почему адрес должен делиться на 16. Я запустил этот код на своей машине и address of g: 0x7ffcccbd8198. Я также не понимаю, почему это было бы в целом правдой, поскольку это означало бы, что если бы у вас был массив структур, каждая из них была бы дополнена до 16 байтов, чтобы все их адреса были выровнены по 16 байтов, но, конечно, это не дело. - person sevko; 26.09.2019
comment
(У меня 64-битная машина fyi) - person sevko; 27.09.2019
comment
@sevko Причина в том, что максимально возможный член составляет 16 байт. Но когда структура содержит только один символ в качестве члена, она может начинаться с любого адреса, я обновил ответ этим. Что касается вашего вывода, я не уверен. Я сделал больше тестов, и они по-прежнему следуют правилам. И я также не совсем уверен, есть ли у массива структур разные правила. Честно говоря, я не специалист по c, большую часть времени использую в работе другие языки. Не могли бы вы задать еще один вопрос, чтобы подтвердить это, и можете обновить ответ, если окажется, что приведенные выше правила неверны? - person user218867; 27.09.2019
comment
Before each individual member, there will be padding so that to make it start at an address that is divisible by its size. e.g on a 64-bit system, int should start at an address **divisible by 4**, and long by 8, short by 2. @EricWang согласно приведенному выше правилу int должен начинаться с адреса, кратного 4, а структура g содержит только член int, поэтому адрес g должен делиться на 4. В этом случае адрес g на вашем компьютере делится на 4 и машина @sevko. - person Vencat; 27.12.2019
comment
@sevko @ user218867 Struct address starts from (n * 16) bytes. не соответствует действительности. Адрес структуры начинается с адреса первого члена. Адрес первого члена определяется требованием выравнивания структуры, а структура имеет выравнивание самого широкого члена. Итак, адрес stu_g делится на 4, адрес stu_f делится на 4. Правило Before each individual... не применяется к первому члену структуры (это применяется к переменной, а не к полю). Для struct stu { char c; int *p; }; его адрес начинается с любых адресов, делящихся на 4/8 в зависимости от типа машины, а не с любых адресов. - person jinbeom hong; 25.04.2021

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

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

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

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

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

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

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

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

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

person IanC    schedule 07.08.2016
comment
Это не объясняет упаковку структуры, но довольно хорошо иллюстрирует выравнивание слов процессора. - person David Foerster; 08.08.2016
comment
Вы нарисовали это краской? :-) - person Ciro Santilli 新疆再教育营六四事件ۍ 30.07.2017
comment
@ CiroSantilli709 大 抓捕 六四 事件 法轮功, это было на канитель, но я думаю, что сэкономил бы время, делая это на краске, хотя хаха - person IanC; 31.07.2017
comment
Даже лучше, поскольку открытый исходный код (Y) - person Ciro Santilli 新疆再教育营六四事件ۍ 31.07.2017
comment
Это также очень удобно при чтении структур из файлов. Можно просто прочитать в буфер, а затем memcpy и т. Д. Прямо в структуру. - person user3342816; 19.07.2021

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

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

#pragma pack(n)

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

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

Как правило, будут использоваться упакованные конструкции.

  • чтобы сэкономить место

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

person user2083050    schedule 19.02.2013

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

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

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

person casablanca    schedule 29.11.2010

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

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

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

Структура 1

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

Массив Struct1

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

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

Структура 2

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

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

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

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

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

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

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

person AlphaGoku    schedule 23.01.2020

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

person nmichaels    schedule 29.11.2010

В этом нет никаких возражений! Тот, кто хочет понять предмет, должен сделать следующее:

person snr    schedule 09.03.2019

Эти конструкции набиты или набиты?

Они мягкие.

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

Однако для этого потребуется, чтобы и sizeof(int), и sizeof(char) были четырьмя (для получения двенадцати и восьми размеров). Вся теория разваливается, поскольку по стандарту гарантируется, что sizeof(char) всегда единица.

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


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

Когда этого требует реализация компилятора. Компиляторы могут вставлять отступы между полями и после последнего поля (но не перед первым полем).

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

Обычно вы можете управлять упаковкой / заполнением (что на самом деле является противоположными сторонами одного и того же спектра) с помощью специфичных для реализации функций, таких как #pragma pack. Даже если вы не можете сделать это в своей конкретной реализации, вы можете проверить свой код во время компиляции, чтобы убедиться, что он соответствует вашим требованиям (с использованием стандартных функций C, а не материалов, специфичных для конкретной реализации).

Например:

// C11 or better ...
#include <assert.h>
struct strA { char a; int  b; char c; } x;
struct strB { int  b; char a;         } y;
static_assert(sizeof(struct strA) == sizeof(char)*2 + sizeof(int), "No padding allowed");
static_assert(sizeof(struct strB) == sizeof(char)   + sizeof(int), "No padding allowed");

Что-то вроде этого откажется компилироваться, если в этих структурах есть какие-либо отступы.

person paxdiablo    schedule 20.01.2021

Переменные хранятся по любым адресам, кратным его выравниванию (как правило, его размеру). Итак, заполнение / упаковка не только для структуры. Фактически, все данные имеют собственное требование выравнивания:

int main(void) {
    // We assume the `c` is stored as first byte of machine word
    // as a convenience! If the `c` was stored as a last byte of previous
    // word, there is no need to pad bytes before variable `i`
    // because `i` is automatically aligned in a new word.

    char      c;  // starts from any addresses divisible by 1(any addresses).
    char pad[3];  // not-used memory for `i` to start from its address.
    int32_t   i;  // starts from any addresses divisible by 4.

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

// Example for rule 1 below.
struct st {
    char      c;  // starts from any addresses divisible by 4, not 1.
    char pad[3];  // not-used memory for `i` to start from its address.
    int32_t   i;  // starts from any addresses divisible by 4.
};

// Example for rule 2 below.
struct st {
    int32_t   i;  // starts from any addresses divisible by 4.
    char      c;  // starts from any addresses.
    char pad[3];  // not-used memory for next `st`(or anything that has same
                  // alignment requirement) to start from its own address.
};
  1. Первый член структуры всегда начинается с любых адресов, которые делятся на собственное требование выравнивания структуры, которое определяется требованием выравнивания наибольшего члена (здесь 4, выравнивание int32_t). С обычными переменными дело обстоит иначе. Обычные переменные могут начинать любые адреса, кратные их выравниванию, но это не относится к первому члену структуры. Как вы знаете, адрес структуры совпадает с адресом ее первого члена.
  2. Внутри структуры могут быть дополнительные завершающие байты с заполнением, в результате чего следующая структура (или следующий элемент в массиве структур) начинается с ее собственного адреса. Подумайте о struct st arr[2];. Чтобы сделать _6 _ (первый член _ 7_), начиная с адреса, делимого на 4, мы должны добавить 3 байта в конец каждой структуры.

Это то, что я узнал из Утраченного искусства упаковки структур.

ПРИМЕЧАНИЕ. Вы можете выяснить, что требуется для выравнивания типа данных с помощью оператора _Alignof. Кроме того, вы можете получить смещение члена внутри структуры с помощью макроса offsetof.

person jinbeom hong    schedule 25.04.2021

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

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