编译链接,从源文件到可执行文件的过程
环境
编译器
这里使用的编译工具是Linux下的gcc/g++。
可以使用命令gcc -v
查看gcc的版本。
编译过程
1.预处理
2.编译
3.汇编
4.链接
准备
3份.c
源文件
test.c
1 |
|
add.c
1 | // 加法函数 |
sub.c
1 | // 减法函数 |
预处理
预处理阶段编译器做的事情:
- 将源文件中包含的头文件展开。例如:#include<stdio.h>,其中#include被称为预处理指令。
- #define 定义符号的替换。例如:#define Max 100,其中#define也被称为预处理指令。
- 删除注释。
预处理命令:gcc -E test.c -o test.i
预处理产生的结果放在了test.i文件中
1 | gcc -E test.c -o test.i |
查看test.i文件的内容,观察行数发现比源文件多了800多行
头文件被展开,注释被删除,define定义的符号被替换
编译
把代码翻译成汇编语言
编译命令:gcc -S test.i -o test.s
1 | gcc -S test.i -o test.s |
查看test.s文件的内容,代码已经全部变成了汇编语言
汇编
把汇编指令翻译成二进制指令
命令:gcc -c test.s -o test.o
查看test.o文件的内容,里面全部变成了二进制指令
链接
生成可执行文件
命令:gcc test.o add.o sub.o -o test
执行程序
直接生成可执行文件
其实可以省略这些步骤,一步直接生成可执行文件
gcc test.c add.c sub.c -o example.so
深度剖析
符号汇总
在编译阶段会进行符号汇总,这里的符号是程序中的变量名、函数名,为后面的汇编和链接阶段做准备
形成符号表
在汇编阶段会形成符号表,如test.o和可执行程序的文件格式都是ELF文件格式
每个.o文件里面都有一个符号表,符号表里面有编译过程中记录的符号,并且还有与符号相关联的地址。
可以利用readelf工具来查看二进制文件
查看test.o文件的符号表
readelf -s test.o
可以看到有main,Add,printf,Sub这些符号
查看add.o文件的符号表
readelf -s add.o
可以看到有Add符号
查看sub.o文件的符号表
readelf -s sub.o
可以看到有Sub符号
链接
在链接阶段编译器会合并段表,合并符号表并重定向。
每个elf文件都是一段一段的,链接就是将每个.o文件的每一段都合并到最终的可执行程序的每一段里面。
将所有.o文件的符号表合并,形成可执行程序的符号表。
当合并完符号表后,可执行程序的符号表就拥有了所有符号有效地址,这样当其调用某个函数的时候就知道应该去那个文件里面找了。