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参数指定输出文件的名字。

让我们通过一个例子来了解这个流程

test.c
1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>

extern int a;

extern void test();

int main()
{
test();
return 0;
}

先预处理它:

1
gcc -E test.c -o test.e.c

可以看到源文件多了许多头文件中的代码。

编译它:gcc -c test.c -o test.o

我们用nm命令来获取其中有哪些符号:

1
nm test.o

输出(第一列是符号地址,第二列是状态,第三列是符号名):

1
2
3
4
                 U a
0000000000000000 B b
0000000000000000 T main
U test

可以看到,a和test的状态是未定义(U),因为gcc目前无法确定这两个符号的地址。

T表示main函数位于代码区,B表示b位于非初始化数据段(bss)中。

此时直接链接会出现若干undefined reference 。

我们再写一个c文件:

test2.c
1
2
3
4
int a;
void test()
{
}

编译,用nm查看相应.o文件:

1
2
0000000000000004 C a
0000000000000000 T test

此时链接就没有错误: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
2
3
linux-vdso.so.1 =>  (0x00007fff629fe000)
libc.so.6 => /lib64/libc.so.6 (0x00000038f9200000)
/lib64/ld-linux-x86-64.so.2 (0x00000038f8e00000)

可以看到,我们的程序用到了三个动态链接库。

最后一个动态链接库严格说来并不是动态链接库,是动态库的装载器。

libc.so属于glibc(一个linux下c语言的运行时库),Windows下对应的文件是msvcrt.dll。

gcc的使用和编译时的符号确定

https://robberphex.com/gcc-compile-and-link/

作者

Robert Lu

发布于

2013-05-17

许可协议

评论