1

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)?

Binarus
  • 4,005
  • 3
  • 25
  • 41
  • It's good practice to declare one variable per line – stark Aug 04 '20 at 11:34
  • this question is answered here: https://stackoverflow.com/a/31067623/5639126 – secret squirrel Aug 04 '20 at 12:19
  • 2
    @secretsquirrel I think this answer is wrong (both from interpreting the gcc doc and by testing it with gcc 10) – ensc Aug 04 '20 at 13:30
  • On that page, there are two answers that both refer to the same part of the gcc documentation *to prove the exact opposite points*. From which we can conclude that the best way to answer this question is to try it yourself and see what you get :) – secret squirrel Aug 04 '20 at 13:35
  • @secretsquirrel I think that this subject is quite complex, and test results in this case might depend on compiler version, other compiler settings and random. When I add this to the fact that there are three syntax variants which are *seemingly* doing the same, I'd rather hear solid statements from people who know for sure instead of trying to test this myself and coming to wrong conclusions :-) – Binarus Aug 04 '20 at 14:12
  • @Binarus: I'd actually expect the gcc folks to take some pains to make the behavior consistent between versions, since otherwise existing code would break. But of course documentation is best (and has been provided in the answer). – Nate Eldredge Aug 04 '20 at 15:24
  • @secretsquirrel: Actually, I think the two answers that cite the gcc docs both reach the same (incorrect) conclusion, that in `int __attribute__((blah)) a, b, c;` the attribute only applies to `a`. A third answer which claims that the attribute is ignored is also wrong, and the fourth answer doesn't really answer the question at all. If nobody else does then perhaps I will write yet a fifth answer which hopefully will be right. – Nate Eldredge Aug 04 '20 at 15:49
  • @NateEldredge is, in your opinion, ensc's answer correct then ? In that other post you linked back to this question but not to the answer below... – secret squirrel Aug 04 '20 at 16:09
  • Yes, I think ensc is correct. – Nate Eldredge Aug 04 '20 at 16:10
  • 1
    @secretsquirrel I also have done additional research and confirm that ensc is correct. However, his answer cites only passages which state under which circumstances the attribute applies to the particular declared object, not to all objects in the declaration, so this might still be worrying. The solution is in the docs he linked: The GNU docs indeed state under which circumstances the attribute applies to the whole declaration, that is, to all declared objects in the declaration. The respective sentence is very hard to find, though. – Binarus Aug 04 '20 at 16:33
  • @Binarus "The respective sentence is very hard to find, though" You can say that again. I missed it. And also, there are examples in the GNU docs have seperate declarations for more than one variable of the same type, although this doesn't confirm it one way or the other. – secret squirrel Aug 04 '20 at 16:41
  • 1
    @secretsquirrel Would it be appropriate if I would write an additional answer with the respective citation? – Binarus Aug 04 '20 at 17:08
  • @Binarus, yes I think it would, especially as now the former answer has a link back to this page, to make it completely clear. – secret squirrel Aug 04 '20 at 20:19
  • @secretsquirrel Done ... – Binarus Aug 05 '20 at 18:06

2 Answers2

4

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
ensc
  • 6,704
  • 14
  • 22
  • Thank you very much for this extremely well-thought answer and for the citations. I had looked in the wrong place of the documentation, that is, where the the `aligned` attribute is described (https://gcc.gnu.org/onlinedocs/gcc-4.7.0/gcc/Type-Attributes.html). I just saw that there is a link in there which leads to the explanations about the syntax; I didn't notice that link the first time, probably because I was too focused on the specific attribute. – Binarus Aug 04 '20 at 14:24
  • Thanks a lot for this answer. The GNU documentation for the attribute syntax is not easy to read... for me it's almost like if I were reading obfuscated code from a contest. Your explanation really clarifies it. – cesss Feb 20 '21 at 11:01
1

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 :-)

Binarus
  • 4,005
  • 3
  • 25
  • 41