嵌入式C语言自我修养 09:链接过程中的强符号和弱符号

栏目: C · 发布时间: 5年前

内容简介:在一个源文件中引用一个变量或函数,当我们只声明,而没有定义时,一般编译是可以通过的。这是因为编译是以文件为单位的,编译器会将一个个源文件首先编译为 .o 目标文件。编译器只要能看到函数或变量的声明,会认为这个变量或函数的定义可能会在其它的文件中,所以不会报错。甚至如果你没有包含头文件,连个声明也没有,编译器也不会报错,顶多就是给你一个警告信息。但链接阶段是要报错的,链接器在各个目标文件、库中都找不到这个变量或函数的定义,一般就会报未定义错误。当函数被声明为一个弱符号时,会有一个奇特的地方:当链接器找不到这个

9.4 弱符号的用途

在一个源文件中引用一个变量或函数,当我们只声明,而没有定义时,一般编译是可以通过的。这是因为编译是以文件为单位的,编译器会将一个个源文件首先编译为 .o 目标文件。编译器只要能看到函数或变量的声明,会认为这个变量或函数的定义可能会在其它的文件中,所以不会报错。甚至如果你没有包含头文件,连个声明也没有,编译器也不会报错,顶多就是给你一个警告信息。但链接阶段是要报错的,链接器在各个目标文件、库中都找不到这个变量或函数的定义,一般就会报未定义错误。

当函数被声明为一个弱符号时,会有一个奇特的地方:当链接器找不到这个函数的定义时,也不会报错。编译器会将这个函数名,即弱符号,设置为0或一个特殊的值。只有当程序运行时,调用到这个函数,跳转到0地址或一个特殊的地址才会报错。

  1. / / func . c
  2. int a __attribute__ ( ( weak ) ) = 1 ;
  3. / / main . c
  4. int a = 4 ;
  5. void __attribute__ ( ( weak ) ) func ( void ) ;
  6. int main ( void )
  7. {
  8.     printf ( "main:a = %d\n" , a ) ;
  9.     func ( ) ;
  10.     return 0 ;
  11. }
  12. 编译程序,可以看到程序运行结果。
  13. $ gcc - o a . out main . c func . c
  14. main : a = 4
  15. Segmentation fault ( core dumped )

在这个示例程序中,我们没有定义 func() 函数,仅仅是在 main.c 里作了一个声明,并将其声明为一个弱符号。编译这个工程,你会发现是可以编译通过的,只是到了程序运行时才会出错。

为了防止函数运行出错,我们可以在运行这个函数之前,先做一个判断,即看这个函数名的地址是不是0,然后再决定是否调用、运行。这样就可以避免段错误了,示例代码如下。

  1. / / func . c
  2. int a __attribute__ ( ( weak ) ) = 1 ;
  3. / / main . c
  4. int a = 4 ;
  5. void __attribute__ ( ( weak ) ) func ( void ) ;
  6. int main ( void )
  7. {
  8.     printf ( "main:a = %d\n" , a ) ;
  9.      if ( func )
  10.         func ( ) ;
  11.     return 0 ;
  12. }
  13. 编译程序,可以看到程序运行结果。
  14. $ gcc - o a . out main . c func . c
  15. main : a = 4

函数名的本质就是一个地址,在调用 func 之前,我们先判断其是否为0,为0的话就不调用了,直接跳过。你会发现,通过这样的设计,即使这个 func() 函数没有定义,我们整个工程也能正常的编译、链接和运行!

弱符号的这个特性,在库函数中应用很广泛。比如你在开发一个库,基础的功能已经实现,有些高级的功能还没实现,那你可以将这些函数通过 weak 属性声明,转换为一个弱符号。通过这样设置,即使函数还没有定义,我们在应用程序中只要做一个非0的判断就可以了,并不影响我们程序的运行。等以后你发布新的库版本,实现了这些高级功能,应用程序也不需要任何修改,直接运行就可以调用这些高级功能。

弱符号还有一个好处,如果我们对库函数的实现不满意,我们可以自定义与库函数同名的函数,实现更好的功能。比如我们 C 标准库中定义的 gets() 函数,就存在漏洞,常常成为黑客堆栈溢出攻击的靶子。

  1. int main ( void )
  2. {
  3.     char a [ 10 ] ;
  4.     gets ( a ) ;
  5.     puts ( a ) ;
  6.     return 0 ;
  7. }

C 标准定义的库函数 gets() 主要用于输入字符串,它的一个 Bug 就是使用回车符来判断用户输入结束标志。这样的设计很容易造成堆栈溢出。比如上面的程序,我们定义一个长度为10的字符数组用来存储用户输入的字符串,当我们输入一个长度大于10的字符串时,就会发生内存错误。

接着我们定义一个跟 gets() 相同类型的同名函数,并在 main 函数中直接调用,代码如下。

  1. #include < stdio . h >
  2.  char * gets ( char * str )
  3.   {
  4.      printf ( "hello world!\n" ) ;
  5.      return ( char * ) 0 ;
  6.   }
  7. int main ( void )
  8. {
  9.     char a [ 10 ] ;
  10.     gets ( a ) ;
  11.     return 0 ;
  12. }
  13. 程序运行结果如下。
  14. hello

通过运行结果,我们可以看到,虽然我们定义了跟 C 标准库函数同名的 gets() 函数,但编译是可以通过的。程序运行时调用 gets() 函数时,就会跳转到我们自定义的 gets() 函数中运行。

9.5 属性声明:alias

GNU C 扩展了一个 alias 属性,这个属性很简单,主要用来给函数定义一个别名。

  1. void __f ( void )
  2. {
  3.     printf ( "__f\n" ) ;
  4. }
  5. void f ( ) __attribute__ ( ( alias ( "__f" ) ) ) ;
  6. int main ( void )
  7. {
  8.     f ( ) ;
  9.     return 0 ;
  10. }
  1. 程序运行结果如下。
  2. __f

通过 alias 属性声明,我们就可以给 f() 函数定义一个别名 f(),以后我们想调用  f() 函数,可以直接通过 f() 调用即可。

Linux 内核中,你会发现 alias 有时会和 weak 属性一起使用。比如有些函数随着内核版本升级,函数接口发生了变化,我们可以通过 alias 属性给这个旧接口名字做下封装,起一个新接口的名字。

  1. / / f . c
  2. void __f ( void )
  3. {
  4.     printf ( "__f()\n" ) ;
  5. }
  6. void f ( ) __attribute__ ( ( weak , alias ( "__f" ) ) ) ;
  7. / / main . c
  8. void __attribute__ ( ( weak ) ) f ( void ) ;
  9. void f ( void )
  10. {
  11.     printf ( "f()\n" ) ;
  12. }
  13. int main ( void )
  14. {
  15.     f ( ) ;
  16.     return 0 ;
  17. }

当我们在 main.c 中新定义了 f() 函数时,在 main 函数中调用 f() 函数,会直接调用 main.c 中新定义的函数;当 f() 函数没有新定义时,就会调用 __f() 函数。

更多嵌入式教程:QQ群/微信公众号:宅学部落

本教程电子书籍下载地址: https://pan.baidu.com/s/1a6L0cyIQKKLlmIfRw7U6Dg


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

写给大忙人看的C++

写给大忙人看的C++

【美】Brian Overland(布莱恩.奥弗兰德) / 卢涛、李颖 / 电子工业出版社 / 2015-8 / 109.00

《写给大忙人看的C++》全面介绍了C++语言知识,既提供了学习C++语言最新功能的捷径,也为快速找到特定问题的答案提供了便利。《写给大忙人看的C++》简明地描述了C++核心语言和标准库中几乎所有的函数、对象和运算符,一目了然地显示了语法、结构和重要函数的信息,内容组织形式便于快速查找信息。《写给大忙人看的C++》精选了实用的例子来深入地讲解概念,还提供了富有挑战性的练习及参考答案,便于读者举一反三......一起来看看 《写给大忙人看的C++》 这本书的介绍吧!

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

在线进制转换器
在线进制转换器

各进制数互转换器

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具