源代码生成可执行文件的过程
一般从源代码到最后生成可执行文件需要经过四个步骤:
预编译–编译–汇编–链接
四个环节各自生成 :
.i文件 --> .s文件 --> .o文件 --> .out文件
.exe文件
预编译
查看预编译文件命令:
gcc -E 源文件.c -o 源文件.i
预编译的主要工作:
- 处理宏定义 #define,注意空格的加入,以及不要加分号
- 处理条件编译 #if #endif
- 处理头文件 #include,将包含的文件插入到该预编译指令的位置
- 删除注释 // ; /*
- 添加行号与文件名
- 保留所有的#pragma编译器指令,因为编译器需要使用它们
编译
编译的主要工作:
- 词法分析:使用有限状态机的算法,由扫描器扫描字符序列顺序生成一系列的符号,如:a = (b+c) --> a,=,(,b,+,c,)
- 语法分析:利用上下文无关语法(CFG)等方法,将词法分析得到的一系列符号生成对应的语法树
- 语义分析:主要进行类型声明、匹配和转换,判断定义是否循环引用等,是静态语义的分析;动态语义是在运行期判断的,如0不能作为除数等
生成语法树如:
语义分析后:
汇编
查看汇编文件命令:
gcc -S 源文件.c -o 源文件.s
也可以反汇编查看汇编代码:
objdump -d xx.out或者.o,.a,.so等二进制文件
语义分析后会将语法树生成中间代码,如三地址码,然后三地址码根据目标机器生成对应的目标汇编代码;汇编代码优化器会将目标汇编代码优化(如:常量表达式运算1+2后得到一个全新的常量3,使用位移来代替乘法运算等),然后输出目标二进制文件
汇编代码转换成机器代码指令,原则上两组指令一一对应
链接
将目标文件,静态库或者动态库,链接成可执行文件
完成各个编译单元(模块)的拼接
链接的主要工作:
- 地址和空间分配:为目标文件中已定义符号分配内存以及地址
- 符号决议(符号绑定):保证每个目标文件所有引用,声明都能在自身或其它目标文件中能找到唯一的定义,若声明符号未找到定义则报错
- 重定位:所有已声明未定义的符号重新定位地址