gcc的使用和编译时的符号确定
一般来说,我们编译c语言程序要经过编译(预处理)、和链接两步。当然教材上讲的时候还有预处理。
预处理:
1 | gcc -E code.c -o code.e.c |
预处理主要处理文件包含和宏定义等等。
编译:
1 | gcc -c code.c -o code.o |
编译主要把c语句翻译成二进制代码(一般此步骤包括了预处理)。
但是有一些函数的实现没有在预处理过的c语言文件中,类似的还有extern声明的变量,所以现在编译的文件还没有办法执行。这些无法决定位置的函数和变量,在.o文件中统称为未定义的符号。
链接:
1 | gcc code.o -o code |
在这一步我们要把所有.o文件中的未定义符号给确定下来,确定的来源有两种,查看其他.o文件的导出表中查找,从其他.so(dll)文件的导出表中查找。
gcc还有一个重要的参数是-g,表示编译的时候保留调试信息(包括c语句和汇编语句的对应关系,变量的分配)。
gcc的-o参数指定输出文件的名字。
让我们通过一个例子来了解这个流程
1 |
|
先预处理它:
1 | gcc -E test.c -o test.e.c |
可以看到源文件多了许多头文件中的代码。
编译它:gcc -c test.c -o test.o
我们用nm命令来获取其中有哪些符号:
1 | nm test.o |
输出(第一列是符号地址,第二列是状态,第三列是符号名):
1 | U a |
可以看到,a和test的状态是未定义(U),因为gcc目前无法确定这两个符号的地址。
T表示main函数位于代码区,B表示b位于非初始化数据段(bss)中。
此时直接链接会出现若干undefined reference 。
我们再写一个c文件:
1 | int a; |
编译,用nm查看相应.o文件:
1 | 0000000000000004 C a |
此时链接就没有错误:gcc test.o test2.o -o test
但是我们可以看到nm test的结果中仍然有未定义的符号:
1 | nm test |
输出如下:
1 | U __libc_start_main@@GLIBC_2.2.5 |
这涉及到动态链接库技术,程序在执行的时候才能决定符号地址(一般来说,这个符号地址在某个so/dll文件中)。
现在我们来查看程序使用了哪些动态链接库(so/dll文件):
执行
1 | ldd test |
结果如下
1 | linux-vdso.so.1 => (0x00007fff629fe000) |
可以看到,我们的程序用到了三个动态链接库。
最后一个动态链接库严格说来并不是动态链接库,是动态库的装载器。
libc.so属于glibc(一个linux下c语言的运行时库),Windows下对应的文件是msvcrt.dll。
gcc的使用和编译时的符号确定