Почему packed,aligned
в структуре C выполняет заполнение?
Мне нужно прочитать кучу байтов MSB/LSB с удаленного устройства с помощью i2c. Поскольку все данные устройства представляют собой байты, я использую uint8_t
, который представляет ровно 8 бит, то есть 1 байт.
Теперь мне нужно быть уверенным, что структура packed
(т. е. нет «дыры» между элементами структуры), если только я не буду читать данные в том виде, в котором они генерируются на удаленном устройстве.
Затем я добавил aligned
для производительности (т. е. разрешил процессору читать структуру с минимальным доступом к памяти).
Как объясняется здесь Заполнение и упаковка структуры, заполнение выравнивает элементы структуры по «естественным» границам адреса, а упаковка предотвращает заполнение.
Этот простой тест меня удивил:
$ gcc --version
gcc (Debian 10.2.1-6) 10.2.1 20210110
$ cat dumb.c
#include <stdio.h>
#include <stdint.h> // uint8_t
struct struct1 {
uint8_t msb;
} __attribute__((packed));
struct struct2 {
uint8_t msb;
} __attribute__((packed,aligned(4)));
struct struct3 {
uint8_t msb;
uint8_t lsb;
} __attribute__((packed));
struct struct4 {
uint8_t msb;
uint8_t lsb;
} __attribute__((packed,aligned(4)));
int main(void) {
struct struct1 s1;
printf("sizeof(s1) %zu\n", sizeof(s1));
struct struct2 s2;
printf("sizeof(s2) %zu\n", sizeof(s2));
struct struct3 s3;
printf("sizeof(s3) %zu\n", sizeof(s3));
struct struct4 s4;
printf("sizeof(s4) %zu\n", sizeof(s4));
return 0;
}
$ gcc -o dumb dumb.c
$ ./dumb
sizeof(s1) 1
sizeof(s2) 4
sizeof(s3) 2
sizeof(s4) 4
aligned
кажется, «дополняет конец структуры»: ожидается ли это?
s2
начнется с адреса памяти, кратного 4 байтам, но будет иметь размер 1.s4
начнется с адреса памяти, кратного 4 байтам, но будет иметь размер 2.Я читаю эти байты с удаленного устройства, поэтому каждый байт передается с помощью i2c с устройства на ПК: теперь для производительности лучше пойти packed
только во избежание «дополнительного заполнения в конце», или лучше пойти packed,aligned
перенос и чтение дополнительных неиспользованных байтов с «дополненными в конце» каждый раз при передаче?
«Я ожидал, что s2 начнется с адреса памяти, кратного 4 байтам, но будет иметь размер 1.»: Как бы вы ожидали, что struct struct2 s2[2];
будет работать тогда? Я думаю, вам нужен атрибут aligned
в объявлении переменной, а не в ее типе.
Атрибут aligned
в объявлении struct
означает, что любой экземпляр структуры должен начинаться с выровненного адреса.
Это также означает, что если у вас есть массив структур, структура может потребовать завершающего заполнения, чтобы каждый член массива был правильно выровнен.
Хотя атрибут packed
минимизирует любые отступы, используемые в структуре, такие отступы не обязательно могут быть полностью устранены при использовании вместе с aligned
.
Например, если у вас было это объявление:
struct struct2 s2[2];
Если бы у вас не было завершающего отступа, то s2[1]
не было бы выровнено должным образом.
Таким образом, это означает, что вы, как правило, не можете иметь структуру, одновременно упакованную и выровненную, поскольку каждая из них имеет свою собственную константу. Поэтому нужно выбирать упакованный или выровненный.
@fgoussen, packed
и aligned
вместе могут дать вам другой макет, чем любой из них по отдельности. В частности, я ожидаю, что он обычно будет давать все отступы в конце, без каких-либо между членами, в тех случаях, когда только aligned
имеет внутренние отступы в некоторых местах, а packed
не имеет их вообще. Но да, выравнивание ограничивает размер макета структуры, поэтому может потребоваться отступ.
packed
говорит, что нужно упаковать элементы внутри структуры.
aligned
говорит, что объект нужно выровнять, отступив в конце.
Почему
packed,aligned
в структуре C выполняет заполнение?
Потому что требование выравнивания типа данных должно делить его размер. В противном случае вы не сможете формировать массивы этого типа. Если вы навязываете определенное требование к выравниванию, компилятору может потребоваться отрегулировать размер (путем добавления отступов).
Мне нужно прочитать кучу байтов MSB/LSB с удаленного устройства с помощью i2c. Поскольку все данные устройства представляют собой байты, я использую uint8_t, который представляет ровно 8 бит, то есть 1 байт.
Теперь мне нужно быть уверенным, что структура упакована (т. е. нет «дыры» между элементами структуры), если только я не буду читать данные, когда они генерируются на удаленном устройстве.
Возможно, более естественным способом сделать это было бы прочитать каждую из ваших пар MSB/LSB в один uint16_t
, который, как вы можете быть уверены, не имеет внутреннего заполнения. И поскольку вы описываете пары таким образом, я полагаю, вы можете интерпретировать пары как 16-битные числа, чего затем можно легко достичь с помощью функции ntohs()
.
Затем я добавил выравнивание для повышения производительности (т. е. разрешил процессору читать структуру с минимальным доступом к памяти).
Очень вероятно, что это преждевременная оптимизация. Любая задержка, вызванная несовпадением, скорее всего, будет полностью перегружена издержками ввода-вывода.
заполнение выравнивает элементы структуры по «естественным» границам адресов,
Более или менее. Это обычная цель заполнения между элементами в макетах структур, но это не является неотъемлемой характеристикой такого заполнения и не является единственным типом места для заполнения.
и упаковка предотвращает набивку.
Не совсем. Точное значение директив управления упаковкой варьируется в зависимости от реализации C, но вы используете GCC, чье определение таково:
packed
Этот атрибут, прикрепленный к определению типаstruct
,union
или C++class
, указывает, что каждый из его членов [...] размещается так, чтобы минимизировать требуемую память.
более того,
Указание этого атрибута для типов
struct
иunion
эквивалентно указанию атрибутаpacked
для каждого члена структуры или объединения.
... в каком контексте,
Атрибут
packed
указывает, что член структуры должен иметь минимально возможное выравнивание — один бит для битового поля и один байт в противном случае.
Так что нет, это вообще не имеет прямого отношения к заполнению. Принудительное выравнивание для членов (не битовых полей) до 1 байта приводит к тому, что они размещаются без какого-либо начального заполнения, но это ничего не говорит о том, следует ли за ним какое-либо дополнение.
aligned
кажется, «дополняет конец структуры»: ожидается ли это?
Да, как уже обсуждалось. И это не противоречит определению packed
.
- Я ожидал, что
s2
начнется с адреса памяти, кратного 4 байтам, но будет иметь размер 1.- Я ожидал, что
s4
начнется с адреса памяти, кратного 4 байтам, но будет иметь размер 2.
Опять же, тип с требованием выравнивания 4 будет иметь размер, кратный 4, поэтому ваши ожидания были неверными. Однако я думаю, что GCC позволяет вам выравнивать отдельные переменные, поэтому, если вы действительно хотите того, что описываете, вы сможете добиться этого для этих конкретных переменных, переместив атрибут aligned
из типов в соответствующие переменные:
struct struct1 s2 __attribute__((aligned(4)));
struct struct3 s4 __attribute__((aligned(4)));
Но опять же, я думаю, ты слишком сильно беспокоишься по этому поводу. Перемещение байтов через интерфейс i2c займет здесь огромное количество времени.
Я читаю эти байты с удаленного устройства, поэтому каждый байт передается с помощью i2c с устройства на ПК: теперь для производительности лучше пойти
packed
только во избежание «дополнительного заполнения в конце», или лучше пойтиpacked,aligned
перенос и чтение дополнительных неиспользованных байтов с «дополненными в конце» каждый раз при передаче?
Кто сказал, что вы будете читать что-либо в любом дополнении структуры? Вы не должны этого делать. Если вы читаете байты по одной паре за раз, вы все равно этого не сделаете. Если вы читаете последовательности из нескольких пар байтов за одну операцию, вы не можете использовать заполнение. Но опять же, похоже, вы все усложняете, чем нужно. Я бы пропустил структуры и использовал тип uint16_t
для пар байтов. Особенно, если вы читаете многие из них вместе, и в этом случае вам следует использовать uint16_t[]
. Предположим, вы хотите интерпретировать каждую пару как 16-битное число, обработайте каждое из них с помощью ntohs()
после чтения, прежде чем делать что-либо еще.
Если результат недостаточно быстрый, профилируйте его, чтобы определить, где находится узкое место, и поработайте над этим.
Я думаю, вы неверно истолковываете, что делает
aligned
.