目前市面上多数的C/C++ IDE使用的编译工具链都是GCC或者LLVM,包括很多的商业IDE的工具链也是基于优化过的GCC或LLVM。而在用到一些高级编译器特性时,我们需要去了解一些编译器命令,比如GNU C的__attribute__,在uboot和Linux源码中会常用到此命令。
__attribute__实际上是GCC的一种编译器命令,用来指示编译器执行实现某些高级操作。__attribute__可以设置函数属性(Function Attribute)、变量属性(Variable Attribute)和类型属性(Type Attribute)。LLVM也借用了GCC的__attribute__,并进行了扩展。
函数属性可以帮助开发人员向函数声明中添加一些特性,这可以使编译器在错误检查方面增强。变量属性允许对变量或结构体成员使用特定的属性进行修饰,比如结构体的对齐控制。
__attribute__的语法格式
__attribute__ ((attribute-list))
attribute的前面和后面都有两个下划线,后面紧跟两对元括弧, attribute-list是一个用逗号分隔开的属性列表。__attribute__ ((attribute-list))放于声明的尾部“;”之前。
常用属性
1、packed
让编译器在编译时取消结构体的字节优化对齐,按照实际占用的字节数进行对齐。在某些场景用户不希望编译器对字节对齐进行调整,否则处理起来会比较麻烦,那么可以使用该属性。
例如在源码中定义了两个结构
struct unpacked_str
{
uint8_t x;
uint16_t y;
};
struct packed_str
{
uint8_t x;
uint16_t y;
}__attribute__ ((packed));
struct unpacked_str strupkd;
struct packed_str strpkd;
int main(void)
{
printf("%d", sizeof(strupkd));
printf("%d", sizeof(strpkd));
}
使用clang编译,运行时分别输出为4和3。
2、aligned
规定变量或结构体成员最小对齐格式,以字节为单位。让用户自行决定变量的对齐字节数,比如一些处理器架构要求向量表需要按照规定的对齐地址放置,这时用户就需要进行控制了。
在代码中定义了一个32位变量:
uint32_t var_in_8bytes __attribute__((aligned(8)));
查看链接后的map映射文件,可以验证对齐的字节数和地址。
同样,你也可以使用默认的对齐方式。如果aligned后面不紧跟一个指定的数字值,那么编译器将依据你的目标机器情况使用最大最有益的对齐方式。
struct mystr
{
int16_t a[3];
} __attribute__ ((aligned));
3、section
section控制变量或函数在编译时的段名。在嵌入式软件开发时用的非常多,比如有外扩Flash或RAM时,需要将变量或函数放置到外扩存储空间,可以在链接脚本中指定段名来操作。在使用MPU(存储保护)的MCU编程时,需要对存储器划分区域,将变量或代码放置到对应的区域,通常也是通过段操作来实现。
const int identifier[3] __attribute__ ((section ("ident"))) = { 1,2,3 };
void myfunction (void) __attribute__ ((section ("ext_function")))
上述代码分别在编译后,数组和函数所在的段分别为“indent”和“ext_function”。
4、unused
意味着函数或变量很可能未被使用,编译器不会针对这个函数产生警告,可以将其声明在在函数实现中没有使用过的参数上,例如:
int main(int argc __attribute__((unused)), char **argv)
{ ...}
5、used
此属性附加到具有静态存储的变量,意味着即使该变量看起来没有被引用,也必须保留该变量。否则在链接的时候链接器发现某个变量未被引用,会将此变量优化掉。
6、weak
若两个或两个以上全局符号名字一样,而其中之一声明为weak symbol(弱符号),则这些全局符号不会引发重定义错误。当普通符号存在时,链接器会忽略掉弱符号,如果不存在普通符号,则使用弱符号。
更多的__attribute__属性可以参考GCC手册,在我们需要使用到编译器一些高级特性的时候,可以在手册中查找。
第二个参数section控制变量或函数在编译时的段名。在嵌入式软件开发时用的非常多,比如有外扩Flash或RAM时,需要将变量或函数放置到外扩存储空间,可以在链接脚本中指定段名来操作。在使用MPU(存储保护)的MCU编程时,需要对存储器划分区域,将变量或代码放置到对应的区域,通常也是通过段操作来实现。
const int identifier[3] __attribute__ ((section ("ident"))) = { 1,2,3 };
void myfunction (void) __attribute__ ((section ("ext_function")))
上述代码分别在编译后,数组和函数所在的段分别为“indent”和“ext_function”。