type
date
slug
category
icon
password
一、简介
1.1 使用场景
摘自 User\letter-shell\shell.h
摘自 STM32L4xx_HAL_Driver\Inc\stm32l4xx_hal_def.h
摘自 cmsis_armcc.h
摘自 Utilities\cm_backtrace\cmb_def.h
1.2 说明
GNU C 增加了一个__attribute__关键字用来声明一个函数、变量或类型的特殊属性。用于指导编译器在编译程序时进行特定方面的优化或代码检查。
- __attribute__后面是两对小括号
- 多个属性,属性之间用逗号隔开
- 属性声明
- section
- aligned
- packed
- format
- weak
- alias
- noinline
- always_inline
二、属性声明:Section
2.1 查看 section header
- 一个源文件编译一个目标文件,在编译过程中,编译器都会按照这个默认规则,将函数、变量分别放在不同的section中,最后将各个section组成一个目标文件。
- Section 说明
- .text seciton 代码段, 函数定义、程序语句
- .date section 数据段,初始化的全局变量、初始化的静态局部变量
- .bss section BSS段,未初始化的全局变量、为初始化的静态全局变量
2.2 格式
2.3 示例:U-boot镜像自复制分析
U-boot其本身在启动过程中,都会从Flash存储介质上加载自身代码到内存,然后进行重定位,跳到内存 RAM 中去执行。U-boot 是怎么完成代码自复制的呢?或者说它是怎样将自身代码从 Flash 复制到内存的呢?
→ 编译器编译两个变量放置在
.__image_copy_start
和.__image_copy_end
两个 section→ 链接器使用 U-boot 的链接脚本U-boot.lds,将
__image_copy_start
和 __image_copy_end
这两个section,在链接的时候分别放在了代码段 .text 的前面、数据段 .data 的后面,作为 U-boot 复制自身代码的起始地址和结束地址。
→ ENTRY(relocate_code)汇编代码主要完成代码复制的功能,然后进行重定位,最后跳到内存中执行

三、属性声明:aligned
为什么要内存对齐?简化CPU和内存RAM之间的接口和硬件设计。
3.1 操作格式
- 地址对齐的字节数必须是2的幂次方,否则编译就会出错
- 定义一个变量时,编译器会按照默认的地址对齐方式
3.2 示例1:指定变量
>>>
- C2,单字节,但编译器会在c1(00402008)和c2(0040200C)之间空出3个存储单元,造成一定的内存空洞,浪费内存资源。
3.3 示例2:指定结构体
>>>
- 成员b需要4字节对齐,所以编译器在给成员a分配完1字节的存储空间后,会空出3字节,这样三个结构体成员一共占据1+3+4+2=10字节的存储空间。但实尺寸为12,多出来的空间分配在哪呢?
- 根据结构体的对齐规则,要按结构体所有成员中最大对齐字节数或其整数倍对齐,或者说结构体的整体长度要为其最大成员字节数的整数倍,如果不是整数倍则要补齐。 结构体的整体长度要是4的整数倍,要在结构体的末尾补充 2 字节,最后结构体的大小为 12 字节。
调整元素位置。
>>>
若按下面代码所示,为b 添加__attribute__((aligned(4)))属性。
>>>
你会发现,结构体的大小又重新变为12字节。这是因为,我们显式指定short变量以4字节地址对齐,导致变量a的后面填充了3字节空间。int型变量c也要4字节对齐,所以变量b的后面也填充了2字节,导致整个结构体的大小为12字节。
四、属性声明:packed
aligned属性一般用来增大变量的地址对齐,元素之间因为地址对齐会造成一定的内存空洞。而packed属性则与之相反,一般用来减少地址对齐,指定变量或类型使用最可能小的地址对齐方式。

>>>
通过结果我们看到,结构体内各个成员地址的分配,使用最小1字节的对齐方式,没有任何内存空间的浪费,导致整个结构体的大小只有7字节。
也可以对整个结构体添加packed属性,这和分别对每个成员添加packed属性效果是一样的。
五、内核中的aligned、packed声明
在Linux内核源码中,我们经常看到aligned和packed一起使用,即对一个变量或类型同时使用aligned和packed属性声明。
这样做的好处是:既避免了结构体内各成员因地址对齐产生内存空洞,又指定了整个结构体的对齐方式。

程序运行结果
在上面的程序中,结构体data虽然使用了packed属性声明,结构体内所有成员所占的存储空间为7字节,但是我们同时使用了aligned(8)指定结构体按8字节地址对齐,所以编译器要在结构体后面填充1字节,这样整个结构体的大小就变为8字节,按8字节地址对齐。
六、属性声明:weak
GNU C通过weak属性声明,可以将一个强符号转换为弱符号。
6.1 强符号和弱符号
在一个程序中,无论是变量名,还是函数名,在编译器的眼里,就是一个符号而已。符号可以分为强符号和弱符号。
- 强符号:函数名,初始化的全局变量名。
- 弱符号:未初始化的全局变量名。
工程中允许强符号和弱符号同时存在。符号决议时,一般会选用强符号,丢掉弱符号。
同名的符号都是弱符号时,那么编译器该选择哪个呢?谁的体积大,即谁在内存中的存储空间大,就选谁。
6.2 函数的强符号与弱符号
- 在链接阶段,链接器会到其他文件中找这些符号的定义,若未找到,则报未定义错误。
- 当函数被声明为一个弱符号时,会有一个奇特的地方:当链接器找不到这个函数的定义时,也不会报错。

编译程序并运行,可以看到程序运行结果如下。
函数名的本质就是一个地址,在调用func()之前,我们先判断其是否为0,如果为0,则不调用,直接跳过。你会发现,通过这样的设计,即使func()函数没有定义,整个工程也能正常编译、链接和运行!
七、属性声明:format
引用
- 王利涛. 嵌入式C语言自我修养:从芯片,编译器到操作系统. 嵌入式C语言自我修养:从芯片、编译器到操作系统, 2021. - 第6章 GNU C编译器扩展语法精讲
- Author:felixfixit
- URL:http://www.felixmicrospace.top/article/gunc_compiler_attribute
- Copyright:All articles in this blog, except for special statements, adopt BY-NC-SA agreement. Please indicate the source!