本文介绍Linux下如何将高级语言(C/C++)编写的代码编译成二进制可执行文件的过程
预处理
概述
源文件file.c
被预编译器cpp
处理为file.i
文件的过程
终端命令
gcc -E file.c -o file.i
或者 cpp file.c > test.i
处理规则
- 处理所有的条件编译指令
- 将
#include
递归引入的头文件包含进当前文件 - 删掉所有的
#define
,替换宏常量,展开所有的宏定义 - 删掉文件中的注释
- 保留
#pragma
编译器指令 - 添加行号和文件名标识,便于编译器产生调试和
error
/warning
使用的信息
编译
概述
c -> cc1、c++ -> cclplus、java -> jc1
由预编译编译程序(新版本的gcc将预编译和编译统一交给同一个程序处理)将源文件 / 预处理后的文件进行词法分析、语法分析、语义分析、优化后产生汇编代码文件的过程。
终端命令
gcc -S file.c -o file.s
或者gcc -S file.i -o file.s
处理规则
词法分析
词法分析器lex
运用类似有限状态机的算法将源码序列分割成一系列记号。记号可分为:标识符、字面值常量、特殊符号几类。识别的同时,将各类记号分类保存,以备后续使用。
eg:
标识符 -> 符号表
字面常量 -> 文字表
......
语法分析
语法分析器yacc
将扫描器lex
产生的记号使用上下文无关语法进行语法分析,生成语法树。语法树的节点由表达式构成。
这个过程可以检测出表达式是否合法。eg:
- 括号不匹配
- 表达式中缺少操作
若不合法,编译器会报告语法分析阶段的错误。
语义分析
语义分析器可以对静态语义进行分析,静态语义即在编译期间能够确定的语义。与之相对的动态语义为运行期间才能够确定的语义。
静态语义包括:声明和类型的匹配,类型的转换等。如果分析过程中出现类似“将浮点型数据赋值给指针”这种无法进行隐式类型转换的类型不匹配错误,编译器就会报错。
语义分析后,整个语法树的节点都被标识了类型,有时还会在树中插入相应的转换节点。
优化
优化分为两个部分:
- 源代码优化
因为直接在语法树上优化比较困难,也没有必要,所以源代码优化器就将语法树转换成中间代码,在这个过程进行一系列的优化。
中间代码和机器以及运行时环境无关,所以是可以跨平台的。因为它不会包含数据的尺寸,变量地址以及寄存器名字等。
常见的中间代码形式有:三地址码和P-代码。 - 目标代码生成和优化
代码生成器将中间代码转换成目标机器代码,这个过程十分依赖机器,因为不同的机器具有不同的字长,寄存器,数据类型等。
目标代码优化器会使用一系列方法进行优化,例如:合适的寻址方式、使用位移来代替乘除法、删除多余的指令等。
汇编
概述
由汇编器as
将编译后的代码对照翻译成机器码的过程
终端命令
gcc -c file.c -o file.o
链接
链接器 collect2。注意:colletc2是ld的一层包装
此阶段的工作:
- 合并段表
- 符号表的合并和重定位
- 将文件中调用的各种静态库和共享库和当前文件链接在一起,形成可执行文件