简介
有为公司搭建通用软件工程(开发平台)的同学们都知道,公共代码部分往往需要编写一些默认的处理函数,当用户想使用自己编写的处理函数,只需编写同名函数就可以让编译器编译时自动实现将默认函数取代为用户函数。对于这类需求,GCC 的编译属性__attribute__((weak))
为我们提供了解决方案。
更多的 attribute 编译属性:
《attribute-section 编译属性-数据拼接》
《attribute-aligned 编译属性-地址对齐》
《attribute-packed 编译属性-字节对齐》
《attribute-weak 编译属性-弱符号》
《attribute-un/used 编译属性-未用警告》
《attribute-at 编译属性-地址指定》
使用
1、作用对象
__attribute__((weak))
作用对象:
常用于函数。在函数实体前面加入此特定语法声明,则表明此函数为弱函数。
2、应用情景
例如搭建一个单片机通用应用工程时,我们一定要事先编写所有的默认中断处理函数,防止某些情形产生中断而无处理函数造成程序跑飞。当有编写同名的专用中断处理函数时,编译器就使用对应专用函数;当没有编写专用中函数时,编译器则使用默认函数。这样做的好处是公共代码独立统一,在同一平台下所有项目工程共用。例如:STM32 单片机 HAL 驱动库就是一个典型例子,它为我们编写好的所有中断处理函数,但当我们想编写自己中断处理函数时,只需要编写同名函数就可以取代 HAL 库的中断函数,非常方便。
3、编程语法
test_main.c
含有默认的中断函数:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
#include <stdio.h>
#include <stdint.h>
void test_irq(void);
int main(int arg1, char *arg2[])
{
test_irq(); //验证结果
}
//这是默认的中断函数-------
__attribute__((weak)) void test_irq(void)
{
printf("run: default irq func 1111111");
}
|
user_irq.c
用户编写的中断函数:
1
2
3
4
5
6
7
8
|
#include <stdio.h>
#include <stdint.h>
//用户编写的中断函数-------
void test_irq(void)
{
printf("run: user's irq func 2222222");
}
|
弱函数在应用中的两种表现:
1
2
3
4
5
6
7
8
9
|
#【没加】用户编写的中断函数时的编译运行:
gcc test_main.c -o test_main #编译
./test_main.exe #运行
run: default irq func 1111111 #打印结果(弱函数被编译运行)
#【加入】用户编写的中断函数时的编译运行:
gcc test_main.c user_irq.c -o test_main #编译
./test_main.exe #运行
run: user's irq func 2222222 #打印结果(弱函数被取代)
|
4、语法总结
一、关于__attribute__(())
的参数名称,为了防止与其它对象出现同名影响,强烈建议在参数的前后都加上__
两个下划线。
1
2
3
4
5
6
7
8
|
__attribute__((section(x))) 改为 __attribute__((__section__(x)))
__attribute__((at(a))) 改为 __attribute__((__at__(a)))
__attribute__((packed)) 改为 __attribute__((__packed__))
__attribute__((aligned(n))) 改为 __attribute__((__aligned__(n)))
__attribute__((unused)) 改为 __attribute__((__unused__))
__attribute__((used)) 改为 __attribute__((__used__))
__attribute__((weak)) 改为 __attribute__((__weak__))
|
二、关于__attribute__(())
语句书写位置总体原则:书写到修饰对象名称的后面(★修饰其左边的单元体(非每个元素),放到最前面即是修饰整体★),但考虑要跨编译平台使用,强烈建议使用宏定义并且按下面规则使用。
1
2
3
4
5
6
7
8
9
10
|
//【总体原则】+++++++++++++++++++
#define O2O_SECTION(x) __attribute__((__section__(x))) //数据拼接 (对象名称后明声明)
#define O2O_AT(a) __attribute__((__at__(a))) //地址指定 (对象名称后明声明)
#define O2O_PACKED __attribute__((__packed__)) //字节对齐 (对象名称后明声明,强烈建议改用 #pragma pack(push, 1) ... #pragma pack(pop) 的兼容性更好)
#define O2O_ALIGN(n) __attribute__((__aligned__(n))) //地址对齐 (对象整体最前声明)
#define O2O_UNUSED __attribute__((__unused__)) //未用不警告(对象整体最前声明)
#define O2O_USED __attribute__((__used__)) //未用不优化(对象整体最前声明)
#define O2O_WEAK __attribute__((__weak__)) //弱化对象 (对象整体最前声明)
#define O2O_INLINE static __inline //内联函数 (对象整体最前声明,c/h文件中直接编写函数(体),不能外部声明)
|
三、关于__attribute__(())
的__aligned__(n)
参数对【结构体类型】修饰的特殊表现(只是唯一的特殊个案):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
typedef struct obj_1_
{
uint16_t a;
uint8_t b;
}obj_1_t __attribute__((__aligned__(64))); //1.用于[单体]结构体类型的【起始地址】
typedef struct obj_n_
{
uint16_t a;
uint8_t b;
}__attribute__((__aligned__(64))) obj_n_t; //2.用于结构体[组员]类型的【起始地址】和【大小】,也可用于单体结构体!备注:是组员非成员!
// -┬-
obj_1_t aaaaaa; //1.影响[单体结构体]的起始地址对齐,不能用于数组 ├→ ●只是唯一的特殊个案●
obj_n_t bbb[6]; //2.影响结构体[每个组员]的起始地址对齐和大小限制----------------------┘
//3.影响[数组整体]起始地址,但不影响[其它组员]起始地址和大小!
__attribute__((__aligned__(64))) struct obj_1_ ccc[8]; //←┤
__attribute__((__aligned__(64))) struct obj_n_ ddd[8]; //←┘
|
四、关于__attribute__(())
的__section__(x)
参数被【编译器】与【链接器】优化的问题:
【section】修饰的数据段会被【编译器】和【链接器】这两道关卡优化掉,需要增加特别语句加以防止!【used、unused】只针对【编译器】而言,但对于【链接器】无效,也就是说链接器的【-Wl,–gc-sections】参数还是会优化掉没使用的段,除非在【链接脚本】中使用【KEEP】语句特别指出的段才不会被优化掉!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
//1.变量声明
__attribute__((__used__)) const uint8_t aaa __attribute__((__section__(".init.001"))); //增加__used__防止被[编译器]优化
__attribute__((__used__)) const uint8_t bbb __attribute__((__section__(".init.002"))); //增加__used__防止被[编译器]优化
//2.编译链接
gcc -Wl,--gc-sections -o hello hello.c /* -Wl,--gc-sections 会强制优化掉没使用的__section__(函数段/数据段)*/
//3.链接脚本
.text :
{
.............
. = ALIGN(4);
KEEP(*(SORT(.init.*))) /* 使用 KEEP(*(SORT())) 防止被[链接器]优化 */
. = ALIGN(4);
_etext = .;
} >FLASH
|