虽然用C语言这么多年,但强符号,弱符号这类东西没有多大关注,最近关注到,感觉挺实用的。
大家都知道,我们的程序里,不论是变量名,还是函数名,都是符号,它存在于编译链接整个过程,甚至在最终的可执行文件中也有(一般用于程序调试),是各目标文件之间的"粘合剂",符号贯穿了我们的程序从无到有的整个过程。
我们在编译代码的过程中,经常会碰到类似这样错误:
其实这就是因为链接目标文件的过程中,链接器遇到了两个强符号,而同时存在两个相同的强符号,会导致链接器不知道应该选哪一个,所以就直接报错。
那么怎么样的才算强符号,怎么样的才算弱符号呢?
正式开始前,先来了解一下GCC的attribute特性里支持weak属性,这个和符号的强弱有直接关系,关于该属性,GNU的官方定义如下:
The weak attribute causes a declaration of an external symbol to be emitted as a weak symbol rather than a global. This is primarily useful in defining library functions that can be overridden in user code, though it can also be used with non-function declarations. The overriding symbol must have the same type as the weak symbol. In addition, if it designates a variable it must also have the same size and alignment as the weak symbol. Weak symbols are supported for ELF targets, and also for a.out targets when using the GNU assembler and linker.
大概的意思就是,weak属性可以将一个符号声明为弱符号,可以用于函数库中,方便用户定义自己的符号以覆盖库中的符号。
先来看看全局变量的强符号与弱符号,看下下面几个测试用例。按如下方式,生成文件test.c:
#include <stdio.h>
int tc_0;
int tc_0 = 6;
int __attribute__((weak)) tc_0;
int __attribute__((weak)) tc_0=1;
int main(void)
{
printf("tc_0:%drn",tc_0);
return 0;
}
编译产生如下错误:
看来,即便变量定义的时候,赋了weak属性,但是变量初始化了,也会被认为是强符号,尝试将"int __attribute__((weak)) tc_0=1;"这一行定义注释掉:
#include <stdio.h>
int tc_0;
int tc_0 = 6;
int __attribute__((weak)) tc_0;
//int __attribute__((weak)) tc_0=1;
int main(void)
{
printf("tc_0:%drn",tc_0);
return 0;
}
编译通过了,执行结果如下:
这里可以得出,没有初始化的全局变量为弱符号,而初始化过的全局变量为强符号。但是对于赋了weak属性又初始化过的变量,虽然在同一个文件里和初始化过的全局变量同样表现为强符号,但是如果定义在不同的文件里会是什么情况呢?这一点需要确认清楚。因此,再建立文件test2.c:
#include <stdio.h>
int __attribute__((weak)) tc_0 = 1;
此时和前面的现象不同,同样可以编译通过。执行结果和上一次一样,符合预期,因为test2.c的变量被添加了weak属性:
将两个文件里的全局变量强弱属性交换一下,再次验证:
test.c:
#include <stdio.h>
int tc_0;
int __attribute__((weak)) tc_0 = 6;
int __attribute__((weak)) tc_0;
int main(void)
{
printf("tc_0:%drn",tc_0);
return 0;
}
test2.c
#include <stdio.h>
int tc_0 = 1;
编译执行结果如下:
可以确定,在不同的文件中,符号的强弱关系和weak属性强相关,不存在模糊地带,一般的应用或者容易出问题的地方也都是在不同的文件之间。
了解完全局变量,再来了解一下函数。如果直接两个文件有完全一样的函数,肯定会报定义冲突,就不再讨论,主要来看看将其中一个函数添加weak属性的情况
test.c:
#include <stdio.h>
void __attribute__((weak)) tc_0(void)
{
printf("tc_o in test.crn");
return;
}
int main(void)
{
tc_0();
return 0;
}
test2.c
#include <stdio.h>
void tc_0(void)
{
printf("tc_o in test2.crn");
return;
}
编译执行后,结果如下:
可见,可以编译通过,且main函数里实际调用的是test2.c里的函数。
那么这个有什么用呢?其实在GNU的定义里已经提到了,强弱符号最大的用处在于,设计框架代码或函数库的时候,可以将默认接口或变量定义弱符号,用户可在自己的代码里定义自己的接口或变量,这样编译链接的时候链接器会链接用户自己定义的接口或变量。这样可以增加框架或函数库的弹性,更方便满足用户自定义的需求。