C之(6)强弱符号引用

C之(6)强/弱符号/引用

Author: Once Day Date:2023年1月11日

漫漫长路,有人对你微笑过嘛…

参考引用文档:

  • C语言强、弱符号,强、弱引用 - 牧野星辰 - 博客园 (cnblogs.com)

1. 概述

如果在代码里面重复定义变量、全局函数,那么编译器就会报出redefinition of 'xxx'的错误。

注意,这里针对同一作用域才会有冲突。C代码的作用域是按两种情况分类,如下:

  • 一种是语法块作用域,即大括号括起的语句块,其变量都是局部作用域。
  • 另外就是文件作用域,这个和常说的全局作用域很像,但是仅限于C源文件,需要注意,C的源文件是单独编译的。
  • 再就是全局作用域,即其符号作用域为整个二进制文件。

那这里重点关注重定义问题,就是全局作用域。如下面的定义:

int a;
int func(void) {
	....
}

2. 强/弱符号

一般而言,在同一作用域下不能定义同一个变量或函数。

但是GNU C对标准C语言进行了扩展,在GCC中,对于符号(在编译时,变量和函数都被抽象成符号)而言,存在着强符号和弱符号之分。

对于C/C++编译器而言,默认函数和已初始化的全局变量为强符号,而未初始化的全局变量为弱符号。

可以使用__attribute__(weak))来声明一个符号为弱符号

如果gcc发现几个相同的变量,并不会直接报错,而是按下面的规则进行取舍:

  • 当两者都为强符号时,报错:redefinition of "xxx"
  • 当两者为一强一弱时,选取强符号的值。
  • 当两者同时为弱时,选择其中占用空间较大的符号,避免溢出、越界等后果。

一个简单强弱符号例子如下:

int x;
int x = 1;

可以看成函数声明,第一个x是弱符号,第二个x是强符号,因此编译器取值为x=1

int x __attribute__((weak));
float x ;

如这样的使用,也会报错,提示类型复杂。一般该属性对于函数声明使用较多。

强弱符号一般用于库的编译和多文件编译过程,一般用来做桩(stub)函数

下面是一个多文件编译的例子:

//symbol.c
#include 
void __attribute__((weak)) func(void) {
    printf("this is func_a.\n");
}
int main(void) {
    printf("hello world\n");
    func();
}
//symbol2.c
#include 
void func(void) {
    printf("this is func_b.\n");
}

makefile文件如下:

symbol : symbol.c symbol2.c
	gcc -o symbol symbol.c symbol2.c

命令行输出如下:

ubuntu->c-code:$ make symbol 
gcc -o symbol symbol.c symbol2.c
ubuntu->c-code:$ ./symbol 
hello world
this is func_b.

可以看到最终符号链接的对象是symbol2.c中的强符号,对于库来使用也是一样

3. 强/弱引用

这个主要是针对声明使用。编译器在编译阶段只负责将源文件编译成目标文件,即二进制文件,然后由链接器对所有二进制文件进行链接操作。

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

编译器默认所有的变量和函数为强引用,同时编程者可以使用__attribute__((weakref))来声明一个函数,注意这里是声明而不是定义,既然是引用,那么就是使用其他模块中定义的实体,对于函数而言,我们可以使用这样的写法:

__attribute__((weakref)) void func(void);

然后在函数中调用func(),如果func()没有被定义,则func的值为0,如果func被定义,则调用相应func。

在现代的编译器中,有如下的规定:

  • weakref需要伴随着一个别名,别名不需要带函数参数,如果对象函数没有定义,我们可以使用别名来实现函数的定义工作,如果不指定别名,weakref作用等于weak。在后面我们会给出相应的示例以助理解。
  • weakref的声明必须为静态。

即如下的使用方式:

static __attribute__((weakref("test"))) void weak_ref(void);

下面是一个例子来说明这点:

//symbol.c
#include 

static void __attribute__((weakref("func_b"))) func(void);

int main(void) 
{
    printf("hello world\n");
    if (func) {
        func();
    } else {
        printf("func is null.\n");
    }
}

直接编译运行上面文件,有:

ubuntu->c-code:$ gcc -o symbol symbol.c;./symbol
hello world
func is null.

说明当前func_b没有定义的实体,因此引用地址为0

加上下面文件:

//symbol2.c
#include 

void func_b(void)
{
    printf("this is func_b.\n");
}

然后编译运行:

ubuntu->c-code:$ gcc -o symbol symbol.c symbol2.c;./symbol
hello world
this is func_b.

这个时候,func就找到对应的定义实体了

4. 总结

弱符号的机制是用来定义某个桩函数,适合于用户自定义新函数来覆盖原有的功能。

弱引用的机制是用来预先定义某个扩展功能桩函数,用户需要时再进行实际的实体定义,如果不定义,也不会报错。

你可能感兴趣的:(C语言,c语言,开发语言)