__attribute__ в определениях нескольких переменных
У меня вопрос, который лучше всего пояснить на примере. Обратите внимание на следующий код:
unsigned char a,
b;
Очевидно, это определяет две переменные типа unsigned char
.
Если бы я хотел выровнять переменные по 16-байтовым границам, мой первый наивный подход был бы следующим:
__attribute__((aligned(16))) unsigned char a,
b;
Моя проблема в том, что я не уверен, всегда ли компилятор применяет __attribute__((aligned(16)))
к обеим переменным.
Я особенно обеспокоен тем, что весь следующий код компилируется без ошибок или предупреждений:
unsigned char a __attribute__((aligned(16)));
unsigned char __attribute__((aligned(16))) b;
__attribute__((aligned(16))) unsigned char c;
Согласно моим исследованиям, __attribute__((aligned(16)))
делает то же самое с соответствующей переменной в трех строках выше. Но такой слабый синтаксис был бы необычным для C, поэтому я несколько недоверчив.
Возвращаясь к моей исходной проблеме, я понимаю, что легко мог бы избежать неопределенности с помощью чего-то вроде
__attribute__((aligned(16))) unsigned char a;
__attribute__((aligned(16))) unsigned char b;
или возможно
unsigned char a __attribute__((aligned(16))),
b __attribute__((aligned(16)));
Но мне очень хотелось бы знать, достаточно ли добавить __attribute__
украшение один раз при объявлении нескольких переменных, все из которых должны иметь атрибут.
Конечно, этот вопрос касается всех атрибутов (не только aligned
атрибут).
В качестве дополнительного вопроса: считается ли хорошим стилем добавлять такие атрибуты не только к определениям переменных, но и к объявлениям переменных (например, в файлах заголовков)?
2 ответа
Да; обе
__attribute__((aligned(16))) unsigned char a, b;
а также
unsigned char __attribute__((aligned(16))) a, b;
выровнять a
а также b
до 16-байтовой границы. gcc обрабатывает__attribute__
как часть типа (например, const
а также volatile
модификаторы), так что смешанные вещи вроде
char * __attribute__((__aligned__(16))) * a;
тоже возможны.
https://gcc.gnu.org/onlinedocs/gcc/Attribute-Syntax.html говорит:
Список спецификаторов атрибутов может появляться непосредственно перед запятой, = или точкой с запятой, завершающей объявление идентификатора, отличного от определения функции. Такие спецификаторы атрибутов применяются к объявленному объекту или функции.
Поэтому
unsigned char a __attribute__((aligned(16))), b;
будет применяться к a
только но не b
.
В другом случае вроде
unsigned char a, __attribute__((aligned(16))) b;
только b
выравнивается. Вот
Список спецификаторов атрибутов может появляться непосредственно перед декларатором (кроме первого) в списке деклараторов, разделенных запятыми... Такие спецификаторы атрибутов применяются только к идентификатору, перед декларатором которого они появляются
из /questions/14787926/primenyaetsya-li-attribute-ko-vsem-peremennyim-v-obyavlenii/14787932#14787932 применяется.
Чтобы избежать всех двусмысленностей, лучше было бы создать новый тип и использовать его. Например
typedef char __attribute__((__aligned__(16))) char_aligned_t;
char_alignedt d, d1;
С этим примером и вашим
unsigned char a __attribute__((aligned(16))), a1;
unsigned char __attribute__((aligned(16))) b, b1;
__attribute__((aligned(16))) unsigned char c, c1;
gcc создает (gcc -c
) а также readelf
показывает описанные выравнивания
8: 0000000000000010 1 OBJECT GLOBAL DEFAULT COM a
9: 0000000000000001 1 OBJECT GLOBAL DEFAULT COM a1 <<< not aligned!
10: 0000000000000010 1 OBJECT GLOBAL DEFAULT COM b
11: 0000000000000010 1 OBJECT GLOBAL DEFAULT COM b1
12: 0000000000000010 1 OBJECT GLOBAL DEFAULT COM c
13: 0000000000000010 1 OBJECT GLOBAL DEFAULT COM c1
14: 0000000000000010 1 OBJECT GLOBAL DEFAULT COM d
15: 0000000000000010 1 OBJECT GLOBAL DEFAULT COM d1
Все кредиты принадлежат @ensc, потому что 1) его ответ правильный и 2) он направил меня на правильный путь в отношении документации.
Однако приведенные им цитаты говорят о том, что атрибут применяется не ко всему объявлению, а только к соответствующему декларатору. Затем он привел несколько примеров, когда атрибут применялся ко всему объявлению.
Сначала я не понимал, почему и когда это так, но теперь нашел соответствующее утверждение в документации. Его трудно найти, потому что абзац, в котором он находится, длинный и содержит много отвлекающей дополнительной информации.
Пожалуйста, обратите внимание на раздел "Все остальные атрибуты" на этой странице документации GCC. Он содержит следующий абзац (сокращенный и выделенный мной):
Любой список спецификаторов и квалификаторов в начале объявления может содержать спецификаторы атрибутов, независимо от того, может ли такой список в этом контексте содержать спецификаторы класса хранения. [...] Все описатели атрибутов в этом месте относятся к объявлению в целом.[...]
Сложив приведенную выше цитату и цитаты из ответа @ensc, ситуация на удивление проста:
Если
__attribute__
появляется в начале объявления, он применяется ко всему объявлению, то есть ко всем деклараторам / объявленным объектам.Во всех остальных случаях он применяется только к определенному декларатору, в котором он находится, то есть только к соответствующему идентификатору или объекту.
Единственное, что может ввести в заблуждение в приведенной выше цитате, - это термин "начало декларации". В руководстве GCC не объясняется, что именно начинается с объявления.
Вероятно, этот термин заимствован из одной из многих спецификаций C и связанных с ним, но я еще не нашел краткого определения.
По результатам испытаний, в
__attribute__((aligned(16))) unsigned char a,
b;
а также
unsigned char __attribute__((aligned(16))) a,
b;
атрибут считается частью списка спецификаторов и квалификаторов в начале объявления.
Напротив, в
unsigned char a __attribute__((aligned(16))),
b;
очевидно, что атрибут (согласно результатам тестирования) не считается частью списка спецификаторов и квалификаторов в начале объявления.
Меня, как не носителя английского языка, это очень беспокоит:
Я бы счел первую строку в каждом из приведенных выше примеров началом объявления. Примечательно, что я бы считал список спецификаторов и квалификаторов в первой строке третьего примера частью начала объявления, хотя этот список (в данном случае состоящий только из__attribute__
часть) идет после имени идентификатора. Очевидно, я поступил так неправильно.
Пожалуйста, не воспринимайте это как дополнительный вопрос - это скорее дополнительный аспект в этом ответе. Возможно, люди GNU однажды прочтут это и прояснят документы:-)