0

我有一个问题,最好用例子来解释。请考虑以下代码:

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属性)。

作为一个额外的问题,将这些属性不仅添加到变量定义中,而且添加到变量声明中(例如在头文件中)是否被认为是一种好的风格?

4

2 回答 2

4

是的; 两个都

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

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

对齐ab到 16 字节边界。gcc 将处理__attribute__作为类型的一部分(如constvolatile修饰符),以便混合诸如

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

也是可能的。

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

属性说明符列表可能紧接在逗号、= 或分号之前,以终止函数定义以外的标识符的声明。此类属性说明符适用于声明的对象或函数

这就是为什么

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

仅适用于 ,a但不适用于b

在另一种情况下

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

只有b对齐。这里

一个属性说明符列表可能会出现在一个以逗号分隔的声明符列表中的声明符(第一个除外)之前......这样的属性说明符仅适用于它们出现在其声明符之前的标识符

来自https://stackoverflow.com/a/31067623/5639126适用。

为了避免所有的歧义,最好创建一个新类型并使用它。例如

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
于 2020-08-04T13:48:02.677 回答
1

所有功劳都归于@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 的人有一天正在阅读这篇文章并澄清文档 :-)

于 2020-08-05T15:45:26.400 回答