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

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

内容简介:在一个源文件中引用一个变量或函数,当我们只声明,而没有定义时,一般编译是可以通过的。这是因为编译是以文件为单位的,编译器会将一个个源文件首先编译为 .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


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

查看所有标签

猜你喜欢:

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

Introduction to the Design and Analysis of Algorithms

Introduction to the Design and Analysis of Algorithms

Anany Levitin / Addison Wesley / 2011-10-10 / USD 117.00

Based on a new classification of algorithm design techniques and a clear delineation of analysis methods, Introduction to the Design and Analysis of Algorithms presents the subject in a coherent a......一起来看看 《Introduction to the Design and Analysis of Algorithms》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具