__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 однажды прочтут это и прояснят документы:-)

Другие вопросы по тегам