Отличается ли структура C, состоящая из всех упакованных полей, кроме первого, от упакованной структуры?
Среди атрибутов общего типа GCC предоставляет
packed
:
Этот атрибут, прикрепленный к
struct
[...] определение типа указывает, что каждый из его членов (кроме битовых полей нулевой ширины) размещается для минимизации требуемой памяти. Этоэквивалентно указаниюpacked
атрибут на каждом из членов.
Кроме того, из стандарта C18 (§ 6.7.2.1, 15-17):
Внутри объекта структуры небитовые [...] поля имеют адреса, возрастающие в порядке их объявления. Указатель на объект структуры, преобразованный соответствующим образом, указывает на его начальный член [...], и наоборот. Внутри объекта структуры может быть безымянное заполнение, но не в его начале. [...] В конце структуры может быть безымянный отступ [...].
Итак, учитывая, что в начале структуры нет отступов, используя
packed
на его первом члене кажется лишним. Взаимно (и в этом моя проблема), используя
packed
на всех его членах, кроме первого, кажется излишне сложным и эквивалентным использованию
packed
на самой структуре (мы предполагаем, что все элементы имеют целочисленный тип (ы)).
Однако я несколько раз встречал код, похожий на:
struct S {
unsigned a;
unsigned b __attribute__ ((__packed__));
unsigned c __attribute__ ((__packed__));
unsigned d __attribute__ ((__packed__));
};
и я не могу понять, почему автор предпочел такую борьбу
struct S {
unsigned a;
unsigned b;
unsigned c;
unsigned d;
} __attribute__ ((__packed__));
Дело в выравнивании самой конструкции,
packed
на первый член перешить? Это исходит от чего-то другого, кроме заполнения и выравнивания, о чем я не думал (например, причуда в более старых версиях GCC)? С другой стороны, если они эквивалентны, как я могу быть в этом уверен, учитывая, что документация здесь, ИМХО, мало помогает?
Хотя это не является достоверным доказательством их эквивалентности, я попытался скомпилировать оба
struct
s для разных архитектур (x86, x86_64, Aarch64), и казалось, что они всегда используют одну и ту же схему.
1 ответ
Если первый член не упакован, он все еще имеет свое первоначальное требование выравнивания, которое может быть больше одного байта, и поэтому структура имеет по крайней мере это требование выравнивания, и это также может потребовать заполнения в конце структуры.
За:
struct S {
unsigned a;
unsigned b __attribute__ ((__packed__));
unsigned c __attribute__ ((__packed__));
unsigned d __attribute__ ((__packed__));
};
GCC 10.2 для x86-64 говорит
_Alignof(struct S)
равно 4. В то время как, если
a
или вся конструкция отмечена
__attribute__ ((__packed__));
, он говорит, что выравнивание равно 1.
Если мы изменим последний член на
char
:
struct S {
unsigned a;
unsigned b __attribute__ ((__packed__));
unsigned c __attribute__ ((__packed__));
char d __attribute__ ((__packed__));
};
тогда GCC сообщает, что его размер составляет 16 байтов, показывая, что он добавил три байта заполнения. Если либо
a
или вся конструкция отмечена
__attribute__ ((__packed__))
, размер изменится на 13.