C语言中的强符号和弱符号介绍

 更新时间:2015年03月17日 10:09:38   投稿:junjie  
这篇文章主要介绍了C语言中的强符号和弱符号介绍,本文用多个实例来讲解强符号和弱符号,需要的朋友可以参考下

之前在extern “C” 用法详解中已经提到过符号的概念,它是编译器对变量和函数的一种标记,编译器对C和C++代码在生产符号时规则也是不一样的,符号除了本身名字的区别外,还有强符号和弱符号之分

我们先看一段简单的代码

复制代码 代码如下:

/* test.c */ 
void hello(); 
int main() 

    hello(); 
    return 0; 


很显然,这段代码是没法链接通过的,它会报错undefined reference to hello,说的是hello未定义,因为这里我们只声明了函数hello,而没有定义它。但是我们把代码稍作修改如下
复制代码 代码如下:

__attribute__((weak)) void hello(); 
int main() 

    hello(); 
    return 0; 


这时你会发现,编译链接都可通过,但是运行会报错,因为这时我们将hello声明为了弱符号,在链接时弱符号会被链接器当做0,执行一个地址为0的函数当然会报错,改为如下代码就不会报错了,只是它没有任何输出
复制代码 代码如下:

__attribute__((weak)) void hello(); 
int main() 

    if(hello) 
        hello(); 
    return 0; 

编译器认为,函数和初始化了的全局变量为强符号,未初始化的全局变量为弱符号,链接器在处理强符号和弱符号时有如下规则

1.不同目标文件中,不允许有同名的强符号
2.如果一个符号在某个目标文件中是强符号,在其它目标文件中为弱符号,选择强符号
3.如果一个符号在所有目标文件中都是弱符号,选择占用空间最大的,比如目标文件A中有double global_var,文件B中有int global_var,double占用8字节,大于int的4字节,A和B链接后,符号global占8字节

对此我们可以简单的验证一下,有如下两个文件

复制代码 代码如下:

/* 1.c */ 
char global_var; 
int main() 

    return 0; 

 
/* 2.c */ 
int global_var; 

全局变量global_var在两个文件中都没有初始化,因此都是弱符号,执行编译命令gcc 1.c 2.c,用readelf查看符号表readelf -s a.out,为了查看方便我们只输出最后几行

复制代码 代码如下:

Num:    Value          Size Type    Bind   Vis      Ndx Name 
62: 0000000000600818     4 OBJECT  GLOBAL DEFAULT   25 global_var 
63: 0000000000400474    11 FUNC    GLOBAL DEFAULT   13 main 
64: 0000000000400358     0 FUNC    GLOBAL DEFAULT   11 _init 

这里符号global_var占用的size是4,说明链接器选择的是占用空间更大的int global_var,我们再稍作修改,将1.c中的全局变量初始化,如下

复制代码 代码如下:

/* 1.c */ 
char global_var = 1; 
int main() 

    return 0; 

 
/* 2.c */ 
int global_var; 

这时1.c中的global_var为强符号,2.c中的global_var为弱符号,同样编译之后用readelf查看符号表readelf -s a.out如下

复制代码 代码如下:

Num:    Value          Size Type    Bind   Vis      Ndx Name 
62: 0000000000600818     1 OBJECT  GLOBAL DEFAULT   25 global_var 
63: 0000000000400474    11 FUNC    GLOBAL DEFAULT   13 main 
64: 0000000000400358     0 FUNC    GLOBAL DEFAULT   11 _init 

此时符号global_var占用的size是1,说明链接器选择的是强符号

在写代码时应该尽量避免有不同类型的符号,否则会引发非常诡异且不易察觉的错误,为了避免可以采取如下措施:

1.上策:消除所有的全局变量
2.中策:将全局变量声明为static类型,并提供接口供访问
3.下策:全局变量一定要初始化,哪怕初始化为0
4.必备:打开gcc的-fno-common选项,它会禁止有不同类型的符号

说了这么多,好像在说应该尽量用强符号,那弱符号有什么用呢,所谓存在即合理,有时候我们甚至需要显示定义弱符号,这对库函数会非常有用,比如库中的弱符号可以被用户自定义的强符号覆盖,从而实现自定义的库版本,或者在使用某些扩展功能时,用户可以定义一个弱符号,当链接了该功能时,功能模块可以正常使用,如果去掉功能模块,程序也可正常链接,只是缺少某些功能而已,比如我们可以通过下面的代码判断程序是否链接了pthread库,从而决定执行什么样的操作

复制代码 代码如下:

/* test.c */ 
#include <stdio.h> 
#include <pthread.h> 
 
__attribute__((weak)) int pthread_create(  
    pthread_t*,  
    const pthread_attr_t*,  
    void*(*)(void*),  
    void*); 
 
int main() 

    if (pthread_create) 
    { 
        printf("This is multi-thread version!\n"); 
    } 
    else 
    { 
        printf("This is single-thread version!\n"); 
    } 
    return 0; 

编译运行结果如下

复制代码 代码如下:

$ gcc test.c 
$ ./a.out 
This is single-thread version! 
$ gcc test.c -lpthread 
$ a.out 
This is multi-thread version! 

相关文章

  • C++实现酒店管理系统

    C++实现酒店管理系统

    这篇文章主要为大家详细介绍了C++实现酒店管理系统,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-08-08
  • C++中的策略模式浅析

    C++中的策略模式浅析

    策略模式属于C++设计模式中行为模式之一,该模式定义了一系列算法,并将每个算法封装起来,使它们可以相互替换。本文将通过示例详细讲解这一模式,需要的可以参考一下
    2023-02-02
  • 通过先序遍历和中序遍历后的序列还原二叉树(实现方法)

    通过先序遍历和中序遍历后的序列还原二叉树(实现方法)

    下面小编就为大家带来一篇通过先序遍历和中序遍历后的序列还原二叉树(实现方法)。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-06-06
  • C++实例详解lambda表达式的使用

    C++实例详解lambda表达式的使用

    Lambda表达式是现代C++在C ++ 11和更高版本中的一个新的语法糖 ,在C++11、C++14、C++17和C++20中Lambda表达的内容还在不断更新。 lambda表达式(也称为lambda函数)是在调用或作为函数参数传递的位置处定义匿名函数对象的便捷方法
    2022-05-05
  • C++中的for-each循环使用

    C++中的for-each循环使用

    范围循环是C++11引入的特性,用于简化数组和容器的遍历过程,它通过直接操作元素而不是使用索引或迭代器,范围循环可以使用引用或const修饰符来控制元素的修改权限,适用于所有支持begin()和end()方法的容器,该循环方式不适用于未提供这些方法的C++98/03容器
    2024-09-09
  • QT quick-Popup弹出窗口自定义的实现

    QT quick-Popup弹出窗口自定义的实现

    本文主要介绍了QT quick-Popup弹出窗口自定义的实现,文中通过示例代码介绍的非常详细,需要的朋友们下面随着小编来一起学习学习吧
    2021-07-07
  • C++详细讲解print缓冲区的刷新

    C++详细讲解print缓冲区的刷新

    这篇文章主要介绍了print缓冲区刷新问题,实现代码简单易懂,具有很好的参考价值,希望对大家有所帮助,需要的朋友可以参考下
    2022-05-05
  • c++入门必学算法之快速幂思想及实现

    c++入门必学算法之快速幂思想及实现

    快速幂相较于普通的幂,具有占用空间少,效率更高等优点,全面碾压普通的幂,下面这篇文章主要给大家介绍了关于c++入门必学算法之快速幂思想及实现的相关资料,需要的朋友可以参考下
    2022-11-11
  • VSCode 搭建 Arm 远程调试环境的步骤详解

    VSCode 搭建 Arm 远程调试环境的步骤详解

    这篇文章主要介绍了VSCode 搭建 Arm 远程调试环境的步骤详解,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-04-04
  • C语言中sizeof()与strlen()的区别详解

    C语言中sizeof()与strlen()的区别详解

    这篇文章主要给大家介绍了关于C语言中sizeof()与strlen()区别的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-12-12

最新评论