之前对C程序的编译过程一直似懂非懂,今天终于动手验证了一把心中的疑惑,第一个疑惑是:一个普通的 .c 文件(不含main())编译出来的 .o 文件是怎么被链接的?比如我们有如下两个文件:test.c 和 mlib.c,其中test.c中包含main函数,mlib.c包含一个全局变量和一个函数:
mlib.c:
int i = 2;
void fun(){}
test.c:
#include<stdio.h>
extern i;
void main(){printf("%p",&i);}
现在我们单独编译mlib.c文件生成一个mlib.o文件,然后使用nm命令观察mlib.o的符号表,以下是输出结果:
00000000 T fun
00000000 D i
该符号表中输出了两个符号,分别是fun和i,二者的偏移地址均为0. 作为对比,现在单独编译test.c文件,然后使用nm命令查看test.o文件,输出结果如下:
U i
00000000 T main
U printf
该符号表中输出了两个未决义符号:printf 和 i,所以这两个符号的偏移量也是未知的。当然,在最终的可执行文件中,这些符号的地址都应该是确定的,这个工作是连接器完成的,下面我们将mlib.o和test.o链接在一起(gcc -o test test.o mlib.o),然后使用nm查看生成的test(nm test):
080483c0 t frame_dummy
08048404 T fun
0804a014 D i
080483e4 T main
U printf@@GLIBC_2.0
其实输出了一大堆,这里只看我们感兴趣的,可以看到已经给 i 分配了位于0804a014处的一块内存,也给 fun 分配了相应的内存,大家或许注意到printf的位置仍然是未知的,因为printf是动态链接的,而动态链接是在程序执行过程中完成的,我们可以执行一下test,输出结果如下:
0x804a014
可以看到输出的地址确实是 i 的地址,因此链接之后便给相应的变量、函数分配了内存,程序执行时操作系统将直接把可执行文件载入到内存中执行。