1分钟理清C/C++的弱符号和强符号,弱引用和强引用

目录

1、强符号和弱符号     

2、强引用和弱引用

3、作用


1、强符号和弱符号     

        在编程中碰到一种情况叫符号重复定义。多个目标文件中含有相同名字的全局变量的定义,那么这些目标文件链接的时候就会出现符号重复定义的错误。比如在目标文件 A 和目标文件 B 都定义了一个全局整型变量global,并且都初始化,那么当 A 和 B 链接时会报如下错误:

        multiple definition of 'global'

        对于C/C++来说,编译器默认函数和初始化了的全局变量为强符号,未初始化的全局变量为弱符号。我们也可以通过GCC的"__attribute__((weak))"来定义任何一个强符号为弱符号。

#include <stdio.h>
 
extern int temp;        // 非强符号也非弱符号
 
int weak;               // 弱符号
int strong = 1;         // 强符号
__attribute__((weak)) int weak2 = 2;    // 弱符号
int main()
{
    //代码
    return 0;   
}

        在默认的符号类型情况下,强符号和弱符号是可以共存的,类似于这样:

int x;
int x = 1;

        编译不会报错,在编译时x的取值将会是1。

        但是使用__attribute__((weak))将强符号转换为弱符号,却不能与一个强符号共存,类似于这样:

int __attribute__((weak)) x = 0;
int x = 1;

        编译器将报重复定义错误。

        强弱符号总结规则如下:

        规则1:不允许强符号被重复多次定义,强弱可以共存。

        规则2:如果一个符号在某个目标文件中是强符号,在其他文件中都是弱符号,那么选择强符号。

        规则3:如果一个符号在所有的目标文件中都是弱符号,那么选择其中占用空间最大的一个。这个其实很好理解,编译器不知道编程者的用意,选择占用空间大的符号至少不会造成诸如溢出、越界等严重后果。

        在编写程序时,通常容易被忽略或者错误认识的一个点是:全局变量不进行初始化,大部分人都认为 C 程序中的全局变量会在程序的启动期间被默认初始化为 0,因此不需要在程序中执行初始化操作。但实际上可能出现的一个问题是,一个全局变量在文件 a 中被定义并初始化为某个值,而在文件 b 中被定义没有初始化,通过上文的讨论可以知道,这并不会报错,此时 b 文件中的全局变量(弱符号)被覆盖,但是它的值并不会是预想中的被初始化为 0,而是 a 中初始化的值,这种情况下就可能造成一些问题。

        当然,这并不一定就说明所有全局变量在定义时就应该初始化为 0,毕竟未初始化的全局变量被放置在 bss 段,对于某些数据结构将会节省大量空间,这是有意义的。只是我们在思考是否需要对全局变量进行初始化的时候需要将上面可能出现的问题考虑进去,根据实际的场景选择合适的方案。

2、强引用和弱引用

        除了强符号和弱符号的区别之外,GNUC还有一个特性就是强引用和弱引用,我们知道的是,编译器在编译阶段只负责将源文件编译成目标文件(即二进制文件),然后由链接器对所有二进制文件进行链接操作。

        在分离式编译中,当编译器检查到当前使用的函数或者变量在本模块中仅有声明而没有定义时,编译器直接使用这个符号,将工作转交给链接器,链接器则负责根据这些信息找到这些函数或者变量的实体地址,因为在程序执行时,程序必须确切地知道每个函数和全局变量的地址。

        如果没有找到该符号的实体,就会报undefined reference错误,这种符号之间的引用被称为强引用.

        强引用:目标文件引用了外部符号,在链接时若未找到定义则报错;则对该外部符号的引用为强引用;

        弱引用:目标文件引用了外部符号,在链接时若未找到定义也不报错;则对该外部符号的引用为弱引用。

        默认的模块中对所有对外部符号(变量和函数)的引用在链接时都是强引用,该符号必须能够被正确的“决议”(或者说“绑定”),否则连接器就会报错,GCC中通过__attribute__((weak))关键字(也可以用__attribute__((weakref)),具体区别可以自己去研究)显示的对一个外部符号的引用定义为弱引用,注意这里是声明而不是定义,既然是引用,那么就是使用其他模块中定义的实体,对于函数而言,我们可以使用这样的写法:

__attribute__((weak)) void func(void);

        这样,即使连接的时候该符号没有正确的找到定义,链接器也不会报错,而只是将该符号的值置为0。但这样得到的可执行程序在执行时会报错,因为当调用弱引用时,弱符号的地址为0,属于非法访问。因此,在程序中调用一个外部符号时,我们可以根据判断函数名是否为0来决定是否执行这个函数,应该先判断其值是否为0,若不为0再调用。如:

#include <stdio.h>
__attribute__((weak)) void foo();  
  
int main()  
{  
    if(foo) 
    {
        foo();
    }
    return 0;
}

3、作用

        通过强弱符号进一步引入的强弱引用。书中关于强弱引用的概述是对于强引用若未定义则链接时肯定会报错,而对于弱引用则不会报错,链接器默认其为0(这一点对于函数好理解,即函数符号所代表入口地址为0;对于变量就要注意了,既然是引用那自然就是地址了,所以同函数一样变量的地址为0而不是变量的值为0)。在定义和声明处指定的函数名、变量名即为对应的符号,而在代码其它处调用函数或使用变量时,则把函数和变量名看作引用,这样一来符号和引用在代码层面上其实就是一个东西,只是根据环境而叫法不同而已。那么强符号对应强引用,弱符号对应弱引用。

        这种弱符号、弱引用的扩展机制在库的实现中非常有用。我们在库中可以使用弱符号和弱引用机制,这样对于一个弱符号函数而言,由于强符号可以覆盖弱符号和强弱符号与强弱引用的关系可知,用户可以自定义扩展功能的函数来覆盖这个弱符号函数。

        同时我们可以将某些扩展功能函数定义为弱引用,当用户需要使用扩展功能时,就对其进行定义,链接到程序当中,如果用户不进行定义,则链接也不会报错,这使得库的功能可以很方便地进行裁剪和组合。


↓↓↓更多技术内容和书籍资料获取,入群技术交流敬请关注“明解嵌入式”↓↓↓ 

猜你喜欢

转载自blog.csdn.net/helloqusheng/article/details/130163513
今日推荐