__attribute__ in definitions of multiple variables

848 views Asked by At

I have a question which is best explained by example. Please consider the following code:

unsigned char a,
              b;

This obviously defines two variables of type unsigned char.

If I would like to make the variables aligned to 16-byte-boundaries, my first naive approach would be this:

 __attribute__((aligned(16))) unsigned char a,
                                            b;

My problem is that I am not sure whether the compiler always applies __attribute__((aligned(16))) to both variables.

I am particularly worried because all of the following code is compiled without errors or warnings:

unsigned char a __attribute__((aligned(16)));
unsigned char __attribute__((aligned(16))) b;
__attribute__((aligned(16))) unsigned char c;

According to my research, __attribute__((aligned(16))) does the same to the respective variable in the three lines above. But such a weak syntax would be unusual for C, so I am somehow mistrustful.

Returning to my original problem, I am aware that I easily could avoid the uncertainty by something like

 __attribute__((aligned(16))) unsigned char a;
 __attribute__((aligned(16))) unsigned char b;

or perhaps

 unsigned char a __attribute__((aligned(16))),
               b __attribute__((aligned(16)));

But I really would like to know whether it is sufficient to add the __attribute__ decoration once when declaring multiple variables which all should have the attribute.

Of course, that question relates to all attributes (not only the aligned attribute).

As a bonus question, is it considered good style to add such attributes not only to the variable definitions, but also to the variable declarations (e.g. in header files)?

2

There are 2 answers

2
ensc On BEST ANSWER

Yes; both

__attribute__((aligned(16))) unsigned char   a, b;

and

unsigned char __attribute__((aligned(16)))    a, b;

align a and b to 16 byte boundary. gcc handles __attribute__ as part of the type (like const and volatile modifiers) so that mixed things like

char * __attribute__((__aligned__(16))) *  a;

are possible too.

https://gcc.gnu.org/onlinedocs/gcc/Attribute-Syntax.html#Attribute-Syntax says:

An attribute specifier list may appear immediately before the comma, = or semicolon terminating the declaration of an identifier other than a function definition. Such attribute specifiers apply to the declared object or function

That is why

unsigned char   a __attribute__((aligned(16))), b;

would apply to a only but not to b.

In another case like

unsigned char   a, __attribute__((aligned(16))) b;

only b is aligned. Here

An attribute specifier list may appear immediately before a declarator (other than the first) in a comma-separated list of declarators ... Such attribute specifiers apply only to the identifier before whose declarator they appear

from https://stackoverflow.com/a/31067623/5639126 applies.

To avoid all the ambiguities, it would be better to create a new type and use this. E.g.

typedef char __attribute__((__aligned__(16)))   char_aligned_t;
char_alignedt d, d1;

With this example and your

unsigned char a __attribute__((aligned(16))), a1;
unsigned char __attribute__((aligned(16))) b, b1;
__attribute__((aligned(16))) unsigned char c, c1;

gcc creates (gcc -c) and readelf shows the described alignments

     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
0
Binarus On

All credits go to @ensc because 1) his answer is correct and because 2) he put me on the right track regarding the documentation.

However, the citations he gave state when the attribute is not applied to the whole declaration, but only to the respective declarator. Then he gave some examples where the attribute was applied to the whole declaration.

I first didn't understand why and when this is the case, but now have found the respective statement in the documentation. It is hard to find because the paragraph it is in is long and has a lot of distracting additional information.

Please consider the section "All other attributes" from this page of the GCC documentation. It contains the following paragraph (shortening and emphasis mine):

Any list of specifiers and qualifiers at the start of a declaration may contain attribute specifiers, whether or not such a list may in that context contain storage class specifiers. [...] All attribute specifiers in this place relate to the declaration as a whole. [...]

Putting together the above citation and the citations from @ensc's answer, the situation is surprisingly simple:

  • If the __attribute__ appears at the start of a declaration, it applies to the whole declaration, that is, to all declarators / declared objects.

  • In all other cases, it applies only to the specific declarator where it is in, that is, only to the respective identifier or object.

The only thing which still could be misleading in the citation above is the term "start of a declaration". The GCC manual does not explain what the start of a declaration exactly is.

Probably this term is borrowed from one of the many C and related specifications, but I haven't found a concise definition yet.

According to test results, in

__attribute__((aligned(16))) unsigned char a,
                                           b;

and

unsigned char __attribute__((aligned(16))) a,
                                           b;

the attribute is considered to be part of the list of specifiers and qualifiers at the start of the declaration.

In contrast, in

unsigned char a __attribute__((aligned(16))),
              b;

the attribute obviously (according to test results) is not considered to be part of the list of specifiers and qualifiers at the start of the declaration.

For me as a non-native English speaker, this is extremely worrying:

I would have considered the first line in each of the examples above to be the start of the declaration. Notably, I would have considered the list of specifiers and qualifiers in the first line of the third example to be part of the start of the declaration, although this list (in this case only consisting of the __attribute__ part) comes after the identifier name. Obviously, I would have been wrong in doing so.

Please don't take this as an additional question - it is meant more as an additional aspect in this answer. Perhaps the GNU folks are reading this one day and clarify the docs :-)