嵌入式C代码属性怎么定义?( 三 )


LOG("hello world ,i am %d ages n", age); /* 前者表示格式字符串,后者表示所有的参数*/6. 属性声明:weakGNU C 通过 weak 属性声明,可以将一个强符号,转换为弱符号 。使用方法如下:
void __attribute__((weak)) func(void);int num __attribute__((weak));在一个程序中,无论是变量名,还是函数名,在编译器眼里,就是一个符号而已,符号可以分为强符号和弱符号 。

  • 强符号:函数名,初始化的全局变量名
  • 弱符号:未初始化的全局变量名 。
在一个工程项目中,对于相同的全局变量名、函数名,我们一般可以归结为以下3种场景:
  • 强符号 + 强符号
  • 强符号 + 弱符号
  • 弱符号 + 弱符号
强符号和弱符号主要用来解决在程序链接过程中,出现多个同名全局变量、同名函数的冲突问题,一般我们遵循以下3个原则:
  • 一山不容二虎
  • 强弱可以共处
  • 体积大者胜出
在一个项目中,不可能同时存在两个强符号 。如果在一个多文件的项目中定义两个同名的函数后者全局变量,那么连接器在链接时就会报重定义错误 。
但是,在一个工程中允许强符号和弱符号同时存在,比如可以定义一个初始化的全局变量和一个未初始化的全局变量,这种写法在编译时是可以编过的 。
编译器对这种同名符号冲突时,在做符号决议时,一般会选择强符号,丢掉弱符号 。
还有一种情况是,在一个工程中,当都是弱符号时,那么编译器该选择哪个呢?谁在内存中存储空间大,就选谁 。
变量的弱符号与强符号
// func1.c int a = 1;int b;void func(void){printf("func.a = %d n", a);printf("func.b = %d n", b);}// main.cint a;int b = 2;void func();int main(){printf("main.a = %dn", a);printf("main.b = %dn", b);func();return 0;}编译后,程序运行结果如下 。可以看出打印的都是强符号的值 。
main.a = 1main.b = 2func.a = 1 func.b = 2 一般不建议在一个工程中定义多个不同类型的同名弱符号,编译时可能会出现各种各样的问题 。也不能同时定义两个同名的强符号,否则会报重定义错误 。我们可以使用GNU C 的扩展 weak 属性,将一个强符号转换为弱符号 。
int a __attribute__((weak)) = 1;函数的强符号与弱符号
链接器对于同名的函数冲突,同样遵循相同的规则 。函数名本身是一个强符号,在一个工程中定义两个同名的函数,编译器肯定会报重定义错误 。但是,我们可以通过weak 属性声明,将其中的一个函数名转换为弱符号 。
//func1.cint a __attribute__((weak)) = 1;void func(void){printf("func.a = %dn", a);}//main.cint a = 4;void __attribute__((weak)) func(void){printf("main.a = %dn", a);}int main(void){func();return 0;}弱符号的用途
在一个源文件中引用一个编号或者函数,当编译器只看到声明,而没看到其定义时,一般编译时不会报错 。在链接阶段,链接器会到其他文件中找到这些符号的定义,若未找到,则报未定义错误 。
当函数被声明一个弱符号时,会有一个奇特地方:当链接器找不到这个函数的定义时,也不会报错 。编译器会将这个函数名,即弱符号,设置为0或者一个特殊值 。只有当程序运行时,调用到这个函数,跳转到零地址或者一个特殊的地址才会报错误,产生一个内存错误 。
如果我们在使用函数前,判断这个函数地址是否为0,即可避免段错误 。你会发现,即使函数未定义也可以正常编过 。
弱符号的这个特性在库函数开发设计中应用十分广泛,如果在开发一个库时,基础功能已经实现,有些高级功能还未实现,那么你就可以将这些函数通过weak 属性声明转换为一个弱符号 。
7. 属性声明:aliasGNU C 扩展了一个 alias 属性,这个属性很简单,主要用来给函数定义一个别名 。
void __f(void){printf("__fn");}void f(void) __attribute__((alias("__f")));int main(void){f();return 0;}在linux 内核中你会发现alias有时候会和weak属性一起使用 。如有些接口随着内核版本升级,函数接口发生了变化,我们可以通过alias属性对旧的接口名字进行封装,重新起一个接口名字 。
//f.cvoid __f(void){printf("__fn");}void f() __attribute__((weak, alias("__f")));//main.cvoid__attribute__((weak)) f(void);void f(void){printf("fn");}int main(){f();return 0;}


推荐阅读