静态编译可以分为下面4个过程:预编译(preprocessing)、编译(compilation)、汇编(assembly)和链接(Linking)。
预编译过程主要处理源文件代码中那些以“#”开始的预编译命令,如#include、#define等。删除所有的注释、处理#if、#else、#define等预编译指令,将所有#define删除,并展开宏定义、添加行号和文件名标识,保留#pragma编译器指令,因为编译器需要使用它们。
GCC的预编译命令是gcc -E hello.c -o hello.i
编译过程就是将预编译处理完的文件进行一系列的词法分析、语法分析、语义分析以及优化后产生相应的汇编代码文件。
GCC的编译命令是gcc -S hello.c -o hello.s
汇编过程生成的文件称为目标文件,一遍以.obj, .o结尾
汇编器的作用就是将汇编代码转变成机器可以执行的指令。
GCC的汇编命令是gcc -c hello.c -o hello.o
链接:链接是一个很容易让人产生迷惑的地方,这里需要一个更加详细的分析。
首先,来看编译器的作用:
扫描--语法分析--语义分析--源代码优化--代码生成--目标代码优化
语法分析分为词法分析和语法分析两个步骤,首先进行词法分析,通过对源文件进行扫描,将源代码中的字符序列分割成一系列的记号,并将相应的记号放入对应的表中。然后语法分析对这些记号进行语法分析,产生语法树(syntax tree),并检查语法中的各种错误。
语义分析:编译器能分析的语义是静态语义,即在编译期能够确定的语义;与之对应的是动态语义,即那些在运行期间才能确定的语义。
静态语义分析包括声明和类型匹配,类型转换等。动态语义则比如给除数赋值为0,只有在运行时才能确定。
源代码优化器通常会将整个语法树转换成中间代码,中间代码接近于目标代码,但它一般是跟目标机器和运行时环境无关的,这样它可以将编译器分为前端和后端,前端负责产生机器无关的中间代码,后端则负责将机器代码转换成目标代码。这样,对于一些跨平台的编译器而言,就可以针对不同的平台使用同一个编译器前端和针对不同平台的后端。
现代的编译器可以将源文件编译成一个未链接的目标文件,然后由链接器将这些目标文件链接起来,形成可执行程序。