编译流程
-
预处理
-
编译
-
汇编
-
链接
预处理(Preprocessing)
- 完成宏替换、文件引入,以及去除空行、注释等,为下一步的编译做准备。也就是对各种预处理命令进行处理,包括头文件的包含、宏定义的扩展、条件编译的选择等。
$ gcc -E test.c -o test.i
# 选项 -E 让 gcc 在预处理结束后停止编译,
# "test.i"文件为预处理后输出的文件。
# -o: 指定输出文件
编译(Compilation)
- 将预处理后的代码编译成汇编代码。在这个阶段中,首先要检查代码的规范性、是否有语法错误等,以确定代码实际要做的工作,在检查无误后,再把代码翻译成汇编语言。
- 编译程序执行时,先分析,后综合。分析,就是指词法分析、语法分析、语义分析和中间代码生成。综合,就是指代码优化和代码生成。
- 大多数的编译程序直接产生机器语言的目标代码,形成可执行的目标文件,也有的是先产生汇编语言一级的符号代码文件,再调用汇编程序进行翻译和加工处理,最后产生可执行的机器语言目标文件。
$ gcc -S test.i -o test.s
# 选项 -S 让 gcc 在编译结束后停止编译过程,
# "test.s”文件为编译后生成的汇编代码。
汇编(Assemble)
- 汇编就是把编译阶段生成的“.s”文件转成二进制目标代码,也就是机器代码(01序列)。
$ gcc -C test.s -o test.o
# 选项 -C 让 gcc 在汇编结束后停止编译过程,
# “test.o”文件为汇编后生成的机器码目标文件。
链接(Linking)
- 链接就是将多个目标文件以及所需的库文件链接生成可执行目标文件的过程。
$ gcc test.o -o test
# -o 本质上是一个重命名选项。
# 不使用 -o 选项时, 默认生成的是 a.out 文件。这里生成的是可执行文件test。
$ ./test
# 执行
一次性执行编译过程
$ gcc test.c -o test
静态库
- 静态库实际就是一些目标文件(一般以.o结尾)的集合,静态库一般以.a结尾,只用于生成可执行文件阶段。
- 在链接步骤中,链接器将从库文件取得所需代码,复制到生成的可执行文件中。这种库称为静态库。其特点是可执行文件中包含了库代码的一份完整拷贝,在编译过程中被载入程序中。缺点就是多次使用就会有多份冗余拷贝,并且对程序的更新、部署和发布会带来麻烦,如果静态库有更新,那么所有使用它的程序都需要重新编译、发布。
#首先生成目标文件
$ gcc -c test.c -o test.o
#使用 ar 命令将目标文件 test.o 打包成 libtest.a 静态库
$ ar rcs libtest.a test.o
# rcs: -r表示更新或新加文件到静态库中;-c表示无论存在与否都创建一个库;-s表示创建文档索引;
ar: creating libtest.a
#使用 ar t libtest.a 查看静态库内容
$ ar t libtest.a
test.o
动态库
- 动态库在链接阶段没有被复制到程序中,而是在程序运行时由系统动态加载到内存中供程序调用。
- 系统只需载入一次动态库,不同的程序可以得到内存中相同动态库的副本,因此节省了很多内存。
# 首先生成目标文件
$ gcc -c test.c
# 使用 -fPIC 和 -shared 生成动态库
$ gcc -shared -fPIC -o libtest.so test.o
# -fPIC: 在动态库中生成位置无关的代码
# -fpic与-fPIC: 为了兼容各个系统,在生成位置无关的代码的时候,应该使用-fPIC(大写)参数。
$ gcc main.c -o main -L. -ltest
# -L. 表示在当前目录下寻找静/动态库
# -ltest 表示链接到静/动态库libtest.a/.so
如果执行./main
时报错:No such file or directory
,就是找不到动态库.so文件,需要配置环境变量LD_LIBRARY_PATH=.
,’.'表示在当前目录下,若在其他目录下则改为其他目录的路径,再执行./main
。
静态库与动态库的区别
- 静态库在程序编译时会链接到目标代码中,程序运行时不再需要静态库,因此体积较大。而且每次编译都需要载入静态代码,因此内存开销大。
- 动态库在程序编译时不会被链接到目标代码中,而是在程序运行时才被载入,程序运行时需要动态库存在,因此体积较小。而且系统只需载入一次动态库,不同程序可以得到内存中相同的动态库副本,因此内存开销小。