在Linux中使用gcc编译“hello.c”文件,只须使用最简单的指令,如下所示
事实上,上述过程可以分解成四个部分:预处理(预编译)、编译、汇编、链接
一、预处理
首先是源代码文件“hello.c”和相关头文件,如stdio.h等被预编译器cpp预编译成一个“.i”文件。
第一步预编译过程相当于如下指令(-E表示只进行预编译)
- $gcc -E hello.c -o hello.i
复制代码
预编译过程主要处理源代码文件中的以“#”开头的预编译指令,如#include,#define等,主要处理过程如下
(1)将所有“#define”删除,并展开所有宏定义
(2)处理所有条件预编译指令,如“#if”,"#ifdef"
(3)删除所有注释
(4)添加行号和文件名标识
(5)保留所有#pragma编译器指令
二、编译
编译过程就是将预处理完的文件进行一系列,词法分析、语法分析、语义分析及优化后生产相应的汇编代码文件。主要处理过程如下
- $gcc -S hello.i -o hello.s
复制代码
现在版本的GCC把预编译和编译两个步骤合成一个步骤,用一个程序来完成这个步骤,对于C语言程序,用ccl这个程序;C++用cclplus;Objective-C用cclobj等,所以gcc只是这些后台程序的包装,它会根据不同的参数要求去调用预编译编译程序cc1、汇编器as、链接器Id。
编译过程一般分为六步:扫描、语法分析、语义分析、源代码优化,代码生成和目标代码优化
1.词法分析
首先源代码被输入到扫描器,进行词法分析,运用一种类似于有限状态机的算法可以将源代码的字符序列分割成一系列的记号,如:关键字、标识符、字面量(数字、字符串等)和特殊符号(加号、等号等)。
对于一些有预处理的语言,比如C语言,它的宏替换和文件包含等工作一般不归入编译器的范围,而是交给一个独立的预处理器。
2.语法分析
语法分析器 将对由扫描器产生的记号进行语法分析,从而产生语法树 (由语法分析器生成的语法树就是以表达式为节点的树)。
3.语义分析
语法分析仅仅完成了对表达式的语法层面的分析,而不能了解这个语句是否有意义,这就需要语义分析器来完成。
编译器所能分析的是静态语义,而运行期间能确定的语义为动态语义,语义分析就是完成声明和类型的匹配,类型的转换。
4.中间语言生成
由于需要进行源代码优化,而在语法树上直接优化比较困难,所以源代码优化器往往将整个语法树转化成中间代码,它是语法树的顺序表示,其实它已经非常接近目标代码了。但是它一股跟目标机器和运行时环境是无关的,比如它不包含数据的尺寸、变量地址和寄存器的名字等。中间代码有很多种类型,在不同的编译器中有着不同的形式,比较常见的有:三地址码 和 P ﹣代码。
中间代码使得编译器可以被分为前端和后端。编译器前端负责产生机器无关的中间代码,编译器后端将中间代码转换成目标机器代码。
5.目标代码生成与优化
源代码级优化器产生中间代码标志着下面的过程都属于编辑器后端。编译器后端主要包括代码生成器和目标代码优化器,代码生成器将中间代码转换成目标机器代码。
目标代码优化器对目标代码进行优化,比如选择合适的寻址方式、使用位移来代替乘法运算、删除多余的指令等。
三、汇编
汇编器是将汇编代码转化成机器可以执行的指令。主要处理过程如下:
- $gcc -c hello.i -o hello.o
复制代码
或者用汇编器as
四、链接
链接的主要内容就是把各个模块之间相互引用的部分都处理好,使得各个模块之间可以正确的衔接
主要过程如下:
注:参考文献《程序员的自我修养》