C语言中的强符号和弱符号介绍
C语言中的强符号和弱符号介绍
发布时间:2016-12-28 来源:查字典编辑
摘要:之前在extern“C”用法详解中已经提到过符号的概念,它是编译器对变量和函数的一种标记,编译器对C和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语言学习
编程开发子分类