工欲善其事必先利其器-C语言拓展–嵌入式C语言(八)
继续来看看新的属性:weak
这个符号知道是弱,但是这个是干嘛的呢?我们先来学习一下强符号和弱符号
文章内容全部来自–>《嵌入式C语言自我修养——从芯片、编译器到操作系统》 王利涛前辈的,超级推荐
强符号和弱符号
GNU C通过weak属性声明,**可以将一个强符号转换为弱符号。**使用方法如下。
void __attribute__((weak)) func(void);
int num __attribute__((weak))
一个程序中,无论是变量名,还是函数名,在编译器的眼里,就是一个符号而已。符号可以分为强符号和弱符号。
● 强符号:函数名,初始化的全局变量名。
● 弱符号:未初始化的全局变量名。
在一个工程项目中,对于相同的全局变量名、函数名,我们一般可以归结为下面3种场景。
● 强符号+强符号。
● 强符号+弱符号。
● 弱符号+弱符号。
强符号和弱符号主要用来解决在程序链接过程中,出现多个同名全局变量、同名函数的冲突问题。一般我们遵循下面3个规则。
● 一山不容二虎。
● 强弱可以共处。
● 体积大者胜出。
在一个项目中,不能同时存在两个强符号。如果你在一个多文件的工程中定义两个同名的函数或全局变量,那么链接器在链接时就会报重定义错误。
但是在一个工程中允许强符号和弱符号同时存在,如你可以同时定义一个初始化的全局变量和一个未初始化的全局变量,这种写法在编译时是可以编译通过的。
编译器对于这种同名符号冲突,在做符号决议时,一般会选用强符号,丢掉弱符号。
还有一种情况就是,在一个工程中,当同名的符号都是弱符号时,那么编译器该选择哪个呢?谁的体积大,即谁在内存中的存储空间大,就选谁。
不建议在一个工程中定义多个不同类型的同名弱符号,编译的时候可能会出现各种各样的问题。
在一个工程中,也不能同时定义两个同名的强符号,否则就会报重定义错误。
可以使用GNU C扩展的weak属性,将一个强符号转换为弱符号。
链接器对于同名函数冲突,同样遵循相同的规则。函数名本身就是一个强符号,在一个工程中定义两个同名的函数,编译时肯定会报重定义错误。
那弱符号能干嘛呢?
在一个源文件中引用一个变量或函数,当编译器只看到其声明,而没有看到其定义时,编译器一般编译不会报错:编译器会认为这个符号可能在其他文件中定义。
在链接阶段,链接器会到其他文件中找这些符号的定义,若未找到,则报未定义错误。
当函数被声明为一个弱符号时,会有一个奇特的地方:当链接器找不到这个函数的定义时,也不会报错。编译器会将这个函数名,即弱符号,设置为0或一个特殊的值。只有当程序运行时,调用到这个函数,跳转到零地址或一个特殊的地址才会报错,产生一个内存错误。
为了防止函数运行出错,我们可以在运行这个函数之前,先进行判断,看这个函数名的地址是不是0,然后决定是否调用和运行,这样就可以避免段错误了。
弱符号的这个特性,在库函数中应用得很广泛。如你在开发一个库时,基础功能已经实现,有些高级功能还没实现,那么你可以将这些函数通过weak属性声明转换为一个弱符号。通过这样设置,即使还没有定义函数,我们在应用程序中只要在调用之前做一个非零的判断就可以了,并不影响程序的正常运行。等以后发布新的库版本,实现了这些高级功能,应用程序也不需要进行任何修改,直接运行就可以调用这些高级功能。
(我还没做好,低调点,等我做好了就理直气壮了)
还有个alias
这个属性很简单,主要用来给函数定义一个别名。
在Linux内核中,你会发现alias有时会和weak属性一起使用。如有些函数随着内核版本升级,函数接口发生了变化,我们可以通过alias属性对这个旧的接口名字进行封装,重新起一个接口名字。
这不是就不用修改函数了,如果我们在main.c中新定义了f()函数,那么当main()函数调用f()函数时,会直接调用main.c中新定义的函数;当f()函数没有被定义时,则调用__f()函数。
这些逻辑还是有点意思
【内容来源】全部来自《嵌入式C语言自我修养》