Учитывать:
struct mystruct_A
{
char a;
int b;
char c;
} x;
struct mystruct_B
{
int b;
char a;
} y;
Размеры конструкций - 12 и 8 соответственно.
Эти конструкции набиты или набиты?
Когда происходит набивка или упаковка?
Учитывать:
struct mystruct_A
{
char a;
int b;
char c;
} x;
struct mystruct_B
{
int b;
char a;
} y;
Размеры конструкций - 12 и 8 соответственно.
Эти конструкции набиты или набиты?
Когда происходит набивка или упаковка?
Заполнение выравнивает элементы структуры по «естественным» границам адреса, например 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.
(Приведенные выше ответы довольно четко объяснили причину, но, кажется, не совсем ясно о размере заполнения, поэтому я добавлю ответ в соответствии с тем, что я узнал из Утерянное искусство упаковки структур, он эволюционировал, чтобы не ограничивать C
, но также применимо к Go
, Rust
. )
Правила:
int
должен начинаться с адреса, кратного 4, а long
на 8, short
на 2.char
и char[]
являются специальными, могут быть любым адресом памяти, поэтому они не нуждаются в заполнении перед ними.struct
, кроме необходимости выравнивания для каждого отдельного члена, размер всей структуры будет выровнен по размеру, кратному размеру самого большого отдельного члена, путем заполнения в конце. long
, то делимый на 8, int
, затем на 4, short
, затем на 2.Порядок участников:
stu_c
и stu_d
из приведенного ниже примера имеют одинаковые элементы, но в разном порядке, что приводит к разному размеру для двух структур.Правила:
(n * 16)
байт. (В приведенном ниже примере вы можете видеть, что все напечатанные шестнадцатеричные адреса структур заканчиваются на 0
.) 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
<The Lost Art of C Structure Packing>
довольно хорошо объясняет правила, даже если подумать, что она немного длиннее, чем этот ответ. Книга доступна бесплатно в Интернете: catb.org/esr/structure-packing
- person user218867; 14.02.2017
Address in memory - for struct
.
- person user218867; 12.02.2018
address of g: 0x7ffcccbd8198
. Я также не понимаю, почему это было бы в целом правдой, поскольку это означало бы, что если бы у вас был массив структур, каждая из них была бы дополнена до 16 байтов, чтобы все их адреса были выровнены по 16 байтов, но, конечно, это не дело.
- person sevko; 26.09.2019
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
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-байтового текстового процессора, но эта концепция применима к другим размерам слов.
Заполнение работает, заполняя промежутки между этими данными, чтобы убедиться, что они выровнены с этими блоками, тем самым улучшая производительность при чтении памяти.
Однако, как указано в других ответах, иногда пространство имеет большее значение, чем сама производительность. Возможно, вы обрабатываете много данных на компьютере, на котором мало оперативной памяти (можно использовать пространство подкачки, но это НАМНОГО медленнее). Вы можете упорядочить переменные в программе до тех пор, пока не будет выполнено наименьшее заполнение (как это было широко показано в некоторых других ответах), но если этого недостаточно, вы можете явно отключить заполнение, что и есть упаковка.
memcpy
и т. Д. Прямо в структуру.
- person user3342816; 19.07.2021
Уплотнение структуры подавляет заполнение структуры, заполнение используется, когда выравнивание имеет наибольшее значение, уплотнение используется, когда больше всего имеет значение пространство.
Некоторые компиляторы предоставляют #pragma
для подавления заполнения или для сжатия до n байтов. Некоторые предоставляют для этого ключевые слова. Обычно прагма, которая используется для изменения заполнения структуры, будет в следующем формате (зависит от компилятора):
#pragma pack(n)
Например, ARM предоставляет ключевое слово __packed
для подавления заполнения структуры. Просмотрите руководство к компилятору, чтобы узнать об этом больше.
Таким образом, упакованная структура - это структура без заполнения.
Как правило, будут использоваться упакованные конструкции.
чтобы сэкономить место
для форматирования структуры данных для передачи по сети с использованием какого-либо протокола (это, конечно, не очень хорошая практика, потому что вам нужно
иметь дело с порядком байтов)
Набивка и упаковка - это два аспекта одного и того же:
В mystruct_A
, предполагая, что выравнивание по умолчанию равно 4, каждый член выравнивается по кратному 4 байтам. Поскольку размер char
равен 1, заполнение для a
и c
составляет 4-1 = 3 байта, в то время как для int b
, который уже составляет 4 байта, заполнение не требуется. Аналогично работает для mystruct_B
.
Правила заполнения:
Почему Правило 2: рассмотрите следующую структуру,
Если бы мы создали массив (из двух структур) этой структуры, в конце не потребовалось бы заполнения:
Следовательно, размер структуры = 8 байт.
Предположим, мы должны были создать другую структуру, как показано ниже:
Если бы мы создали массив этой структуры, у нас было бы 2 варианта количества байтов заполнения, необходимых в конце.
A. Если мы добавим 3 байта в конце и выровняем его для int, а не Long:
Б. Если мы добавим 7 байтов в конце и выровняем его по Long:
Начальный адрес второго массива кратен 8 (т. Е. 24). Размер структуры = 24 байта
Следовательно, выравнивая начальный адрес следующего массива структуры с кратным наибольшему члену (то есть, если бы мы должны были создать массив этой структуры, первый адрес второго массива должен начинаться с адреса, который является кратным самого большого члена структуры. Здесь 24 (3 * 8)), мы можем вычислить количество байтов заполнения, необходимых в конце.
Упаковка структуры выполняется только в том случае, если вы явно указываете компилятору упаковать структуру. Набивка - это то, что вы видите. Ваша 32-битная система дополняет каждое поле до выравнивания слов. Если бы вы сказали своему компилятору упаковать структуры, они бы составили 6 и 5 байтов соответственно. Но не делай этого. Он не переносится и заставляет компиляторы генерировать гораздо более медленный (а иногда даже ошибочный) код.
В этом нет никаких возражений! Тот, кто хочет понять предмет, должен сделать следующее:
- Прочтите Утраченное искусство упаковки структур, написанное Эриком С. Реймондом
- Взгляните на пример кода Эрика
- И последнее, но не менее важное: не забывайте следующее правило заполнения, которое структура выравнивается в соответствии с требованиями выравнивания наибольшего типа.
Эти конструкции набиты или набиты?
Они мягкие.
Единственная возможность, которая изначально приходит на ум, где их можно было бы упаковать, - это если бы 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");
Что-то вроде этого откажется компилироваться, если в этих структурах есть какие-либо отступы.
Переменные хранятся по любым адресам, кратным его выравниванию (как правило, его размеру). Итак, заполнение / упаковка не только для структуры. Фактически, все данные имеют собственное требование выравнивания:
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.
};
4
, выравнивание int32_t
). С обычными переменными дело обстоит иначе. Обычные переменные могут начинать любые адреса, кратные их выравниванию, но это не относится к первому члену структуры. Как вы знаете, адрес структуры совпадает с адресом ее первого члена.struct st arr[2];
. Чтобы сделать _6 _ (первый член _ 7_), начиная с адреса, делимого на 4, мы должны добавить 3 байта в конец каждой структуры.Это то, что я узнал из Утраченного искусства упаковки структур.
ПРИМЕЧАНИЕ. Вы можете выяснить, что требуется для выравнивания типа данных с помощью оператора _Alignof
. Кроме того, вы можете получить смещение члена внутри структуры с помощью макроса offsetof
.
Выравнивание структуры данных - это способ организации данных и доступа к ним в памяти компьютера. Он состоит из двух отдельных, но связанных вопросов: выравнивания данных и структуры данных. обивка. Когда современный компьютер читает или записывает в адрес памяти, он будет делать это фрагментами размером со слово (например, 4-байтовыми фрагментами в 32-разрядной системе) или более крупными. Выравнивание данных означает размещение данных по адресу памяти, равному некоторому кратному размеру слова, что увеличивает производительность системы из-за того, как процессор обрабатывает память. Чтобы выровнять данные, может потребоваться вставить несколько бессмысленных байтов между концом последней структуры данных и началом следующей, что является заполнением структуры данных.
padding
делает вещи больше.packing
делает вещи меньше. Абсолютно другой. - person smwikipedia   schedule 25.06.2017