Reputation: 4405
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)?
Upvotes: 1
Views: 1027
Reputation: 4405
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 :-)
Upvotes: 1
Reputation: 6984
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
Upvotes: 4