在上次介绍了可重定位目标文件的基本知识后,下面来介绍如何将.o文件链接到可执行目标文件。
mismatch-variable.c代码内容:
/* Global strong symbol */
double x = 3.14;
mismatch-main.c代码内容:
#include <stdio.h>
long int x; /* Weak symbol */
int main(int argc, char *argv[]) {
printf("%ld\n", x);
return 0;
}
非常简短的两个模块(.c文件),其中mismatch-variable.c文件只定义了一个全局变量x,为浮点型并赋初值3.14。而mismatch-main.c文件也定义了一个全局变量x,为长整型并没有赋初值。
由于mismatch-main.c模块有主函数main,并且x为赋初值,通过在linux平台上命令行输入gcc -o mismatch-main mismatch-main.c和./mismatch-main可以看到输出为0。
我们再通过命令gcc -Wall -Og -c mismatch-variable.c mismatch-main.c得到两个模块的可重定位目标文件,并输入命令:
gcc -Wall -Og -o mismatch mismatch-main.c mismatch-variable.c
在生成可重定位目标文件后,进行此操作即可得可执行目标文件,这里命名为mismatch。
其中,
- -Wall表示允许发出gcc提供的所有有用的报警信息
- -Og表示启用全局优化
- -o表示设定输出文件名,不加此选项会默认可执行文件名为a.out
注意此处要加上两个可重定位目标文件才能链接成功生成可执行目标文件。
这条命令可以将.o文件链接到可执行目标文件。
此时我们再通过./mismatch却发现输出了一个意想不到的数:
输出的x既不是0也不是3.14,这是为什么呢?这里便涉及到链接器如何解析多重定义的全局符号。
Linux编译系统在编译时,编译器向汇编器输出每个全局符号,其中函数和已初始化的全局变量为强符号,未初始化的全局变量是弱符号。汇编器把这个信息隐含地编码在可重定位目标文件的符号表里。
Linux链接器使用以下规则来处理多重定义的符号名:
- 不允许有多个同名的强符号。 强符号只能被定义一次,否则会链接报错。
- 如果有一个强符号和多个弱符号同名,那么选择强符号。 这样就会有对弱符号的引用被解析为其强定义符号的事情发生,即强符号替代弱符号。
- 如果有多个弱符号同名,那么从这些弱符号中任意选择一个。 这样程序员会不知道选择了哪个弱符号。使用命令gcc -fno-commom链接时,会告诉链接器在遇到多个弱定义的全局符号时输出一条警告信息。
在本例中mismatch-variable.c模块中的x是已初始化的全局变量,为强符号。而mismatch-main.c模块中的x是未初始化的全局变量,为弱符号。这样在两个模块链接的时候会发生强符号替代弱符号的情况。
由于强符号和弱符号在64位机器都是占8个字节64位,且双精度浮点型有特殊的表达方式,使3.14转换成的二进制数以小端方式赋给x,再将此二进制数长整型规则判断时变成了一个截然不同的数。
存储示意图:
该例说明:两个重复定义的变量具有不同类型时,更容易出现难以理解的结果!
所以,符号解析时只能有一个确定的定义(即每个符号仅占一处存储空间)
结论:模块间相互引用容易出错,故应尽量避免使用全局变量。
一定需要用的话,就按以下规则使用:
- 尽量使用本地变量(static),模块内引用不太会出错。
- 全局变量要赋初值,使成为强符号,易查出链接错误。
- 外部全局变量使用extern,以示其引用的定义在其他模块。
部分资料来源:慕课《计算机系统基础(一)》第十一周