嵌入式编程中的 __attribute__ 到底是什么

嵌入式编程中的 attribute 到底是什么

相信阅读嵌入式代码的老铁经常看到一些类型定义、变量、函数有 attribute 标识符,这个标识符号到底是做什么的?有哪些用法,咱们今天就来聊一聊。

attribute 可以指定编译时的细节。其可作用于变量、函数、结构体、结构体成员。
值的注意的是,Attributes 机制并不是 C标准 的一部分。因此,使用 Attributes 的程序有时候不可跨编译器移植。但是,目前市面上多数的 C/C++ IDE使用的编译工具链都是 GCC,包括很多的商业 IDE 的编译工具链也是基于 GCC 优化过的版本。因此 GNU C的__attribute__ 一般是可以兼容的,在 uboot 和 Linux 源码中会常用到此__attribute__机制。

attribute 格式

__attribute__ ((attribute-list)) 

例子:

extern void  die(const char *format,...)    __attribute__( (noreturn) );  // 只含一个属性 noreturn

extern void  die(const char *format,...)    __attribute__( (noreturn,  format(printf, 1, 2)) ); // 含两个属性:noreturn,  format(printf, 1, 2);两个属性以逗号分隔

一些常见的 attribute 属性

attribute((deprecated))

示例:用于版本管理,声明该函数将被丢弃,在编译时会发出提示,提醒开发人员使用最新版本的函数或变量。

int spi_cal_clock(int fapb, int hz, int duty_cycle, uint32_t* reg_o) __attribute__((deprecated));

编译时会提示:attribute_deprecated.c:33: warning: ‘ spi_cal_clock()’ is deprecated。

attribute((always_inline)) 与 attribute ((noinline))

内联函数(inline 函数)详解 中我们介绍了内联函数与非内联函数的区别。attribute((always_inline)) 与 attribute ((noinline)) 均作用于编译器,在编译阶段试图强制使得对应的函数内联或者非内联。
示例:

__attribute__((always_inline)) inline int32_t add(int32_t x, int32_t y) {

​	return (x+y);

}

上面的函数有两个 inline。后面的 inline 可能被编译器忽略(比如 c89 不支持该关键字,C99 支持该关键字)。但前面的 attribute((always_inline)) (GCC 编译器支持的属性) 不会被忽略,否则会报错。

attribute((noreturn))

允许一个函数不执行到 return 语句。

static void __ubsan_default_handler(struct source_location *loc, const char *func)
{
    char msg[60] = {};
    (void) strlcat(msg, "Undefined behavior of type ", sizeof(msg));
    (void) strlcat(msg, func + strlen("__ubsan_handle_"), sizeof(msg));
    esp_system_abort(msg);
}

直接编译上述语句可能触发 warning: this function may return with or without a value。
应使用下述声明说明该函数最终不返回,因为直接触发了 abort 异常,设备会重启,因此不必返回。

static void __ubsan_default_handler(struct source_location *loc, const char *func) __attribute__((noreturn));

attribute((overloadable))

声明支持重载。用于c语言函数,可以定义若干个名称相同,但传递的参数不同的函数,调用时编译器会自动根据参数选择函数原型。

__attribute__((overloadable)) void print(NSString *string)

__attribute__((overloadable)) void print(Int *int)

attribute((alias()))

用于设置函数别名。

int __centon() 
{
  printf("in %s\n",__FUNCTION__);
  return 0;
}

void centon() __attribute__((alias("__centon")));//设置函数别名,原函数是__cencon, 别名是centon. 后面可以使用 centon 代替 __centon.

attribute((nonnull()));

用于声明一个函数的指定参数不能为 null

extern void *
my_memcpy (void *dest, const void *src, size_t len)
        __attribute__((nonnull (1, 2))); // 参数 dest、src 不能为 null,否则编译器会发出警告

attribute((constructor(PRIORITY))) 与 attribute((destructor(PRIORITY)))

构造属性(constructors)和析构属性(destructors)属性,可带优先级(PRIORITY)。可以作用于函数和全局变量对象(或静态变量),这里以函数为例。
带有"构造"属性的函数将在main()函数之前被执行,而声明为"析构"属性的函数则将在main()退出时执行。主要用于在 main() 函数执行前后执行很多的前处理动作或者是后处理动作。

void main_enter1() __attribute__((constructor(99)));//main_enter函数在进入main函数前调用
void main_enter2() __attribute__((constructor(100)));//main_enter函数在进入main函数前调用
void main_exit() __attribute__((destructor));//main_exit函数在main函数返回后调用

上述函数,在进入 main() 之前,先调用 main_enter1(),因为它的优先级高为 99,然后调用 main_enter2(),因为它的优先级次高 100。最后在 main() 执行后,将调用main_exit() 。

对齐属性 attribute((aligned(x))) 与__attribute__((packed))

对齐属性可以用于指定内存的对齐方式。对齐值必须是 2 的整数幂。
如,int8_t foo; 可以被存储在 0x100\0x101\0x102\0x103 任意位置。
但是通过 int8_t foo attribute((aligned(4))); 指定其对齐方式为 4 bytes。然后其对齐就变成 0x100\0x104\0x108 了。
示例,请计算下面四种类型的变量的长度:

typedef struct {
    int8_t var1;
    int32_t var2;
    int8_t var3;
} custom_type1;

typedef struct {
    int8_t var1 __attribute__((aligned(4)));
    int8_t var2 __attribute__((aligned(4)));
    int8_t var3 __attribute__((aligned(4)));
}custom_type2;

typedef struct {
    int8_t var1;
    int32_t var2;
    int8_t var3;
}custom_type3 __attribute__((aligned)); // 自动按字对齐,如果aligned 后面不紧跟一个指定的数字值,那么编译器将依据你的目标机器情况使用最大最有益的对齐方式。ligned 属性使被设置的对象占用更多的空间,相反的,使用packed 可以减小对象占用的空间。需要注意的是,attribute 属性的效力与你的连接器也有关,如果你的连接器最大只支持16 字节对齐,那么你此时定义32 字节对齐也是无济于事的。ligned 属性使被设置的对象占用更多的空间,相反的,使用packed 可以减小对象占用的空间。

typedef struct {
    int8_t var1;
    int32_t var2;
    int8_t var3;
} custom_type4 __attribute__((packed)); // 不自动对齐。此时 sizeof( custom_type4) 应该是 6bytes. 使用该属性可以使得变量或者结构体成员使用最小的对齐方式,即对变量是一字节对齐,对域(field)是位对齐。使用该属性对struct或者union类型进行定义,设定其类型的每一个变量的内存约束。当用在enum类型定义时,暗示了应该使用最小完整的类型。告诉(不是强制)编译器取消结构在编译过程中的优化对齐,按照实际占用字节数进行对齐,是GCC特有的语法。

探究不同对齐方式下,最终结构体的大小。答案在文末。

attribute ((section (xx)))

section控制变量或函数在编译时的段名。在嵌入式软件开发时用的非常多,比如有外扩 Flash 或 RAM 时,需要将变量或函数放置到外扩存储空间,可以在链接脚本中指定段名来操作。在使用MPU(存储保护)的MCU编程时,需要对存储器划分区域,将变量或代码放置到对应的区域,通常也是通过段操作来实现。

提到section,就得说代码存放的基本区域了。在编译器编译之后,代码被划分为不同的段,RO Section(ReadOnly)中存放代码段和常量,RW Section(ReadWrite)中存放可读写静态变量和全局变量,ZI Section(ZeroInit)是存放在RW段中初始化为0的变量。
attribute((section(“section_name”))),其作用是将作用的函数或数据放入指定名为"section_name"对应的段中。

const int identifier[3] __attribute__ ((section ("ident"))) = { 1,2,3 };

void myfunction (void) __attribute__ ((section ("ext_function")))

上述代码分别在编译后,数组和函数所在的段分别为“indent”和“ext_function”而不是通常的 text 段中。

attribute((unused)) 与 attribute((used))

在C程序中,如果定义了一个静态函数\变量a,而没有去使用,编译时会有一个告警:‘a’ defined but not used [-Wunused-function]。

uint16_t acl_length __attribute__((unused));

__attribute__((unused)) static uint32_t get_cause(void)
{
    uint32_t wakeup_cause = REG_GET_FIELD(RTC_CNTL_WAKEUP_STATE_REG, \
                                            RTC_CNTL_WAKEUP_CAUSE);
    return wakeup_cause;
}

意味着函数或变量很可能未被使用,此时编译器根据__attribute__((unused))不会针对这个函数产生警告。
也可以将其声明在函数实现中没有使用过的参数上,例如:

int main(int argc __attribute__((unused)), char **argv)

attribute((used)) 的作用是告诉编译器,我声明的这个符号是需要保留的。被used修饰以后,意味着即使函数没有被引用,在编译链接时下也不会被优化。如果不加这个修饰,那么链接器可能会去掉没有被引用的段。

attribute((weak))

若两个或两个以上全局符号名字一样,而其中之一声明为weak symbol(弱符号),另一个没有添加 weak 修饰符,则这些同名的全局符号不会引发重定义错误。当没有添加 weak 修饰符的对象存在时,链接器会忽略掉弱符号修饰的那个,如果不存在添加 weak 修饰符的对象,则使用 weak 修饰的那个对象。
感兴趣的可以参考我前述的博客:详解 C 语言中的弱符号与弱引用

attribute((const))

该属性只能用于带有数值类型参数的函数上。当重复调用带有数值参数的函数时,由于返回值是相同的,所以此时编译器可以进行优化处理,除第一次需要运算外, 其它只需要返回第一次的结果就可以了,进而可以提高效率。该属性主要适用于没有静态状态(static state)和副作用的一些函数,并且返回值仅仅依赖输入的参数。

__attribute__((const)) int f() {
    return 1;
}

更多的__attribute__属性可以参考GCC手册,在我们需要使用到编译器一些高级特性的时候,可以在手册中查找。

总结

  1. __attribute__是一个编译属性,用于向编译器描述特殊的编译标识,可以用于编译程序时的错误检查或高级优化。它是GNU C特色之一,系统中有许多地方使用到。
  2. 高级嵌入式开发工程师应该对编译的优化选项增加了解,比如 attribute 机制,可以实现更高级的用法,写出更安全可靠的好程序。

对齐属性的答案

custom_type1 len = 12 // 编译器按照默认自动对齐
custom_type2 len = 12 // 每个成员都 4bytes 对齐
custom_type3 len = 12 // 32 位编译器默认 4bytes 对齐
custom_type4 len = 12 // 因为我的编译器禁止这种 packed 优化,因此最终会在编译时提出警告,但仍以最优长度进行对齐

(感谢收藏与点赞,您的支持是我持续创作的动力)

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

物联网老王

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值