引言
通常在我们写好c程序后,使用编译器直接编译运行即可,但是就像如此简单的一句程序gcc test.c同样有其深入复杂的原理,深入理解他们有助于我们更好的理解程序编译执行的过程。通常,这过程细分共有四步,预编译(又叫预处理),编译,汇编,链接。即正常本来是编译和执行,但编译部分又被分为了预编译,编译,汇编三部分。同时编译进行的环境我们叫做翻译环境,执行文件在电脑上的环境称作执环境,现在让我们细节的简要直白展示一下编译的过程。(想要了解更多相关知识可以参考《程序员的自我修养一书》)
一 预编译
我们写c语言程序的时候,不知道大家想过那些以#号开头的指令如何发挥作用嘛,预编译就是主要用来处理源文件中的以“#”开头的预编译指令,他将源文件及相关的头文件预编译为一个.i文件,相当如下命令:
gcc -E hello.c -o hello.i
有如下处理规则:
1.将所有的“#define”删除,并且展开所有的宏定义
2.处理所有条件预编译指令,比如“#if,#ifdef,#elif,#else,#endif”
3.处理“#include预编译指令,将被包含的文件插入到该预编译指令的位置。注意,这个过程是递归进行的,即包含的文件可能还含有其他文件。
4.添加行号和文件名标识,比如#2“helloo.c"2,以便于编译时编译器产生调试用的行号信息及用于编译时残生编译错误被警号时能够显示行号。
5.保留所有#pragram编译器指令,因为编译器要使用他们。
经过预编译后的.i文件不包含任何宏定义,因为所有的宏以及被展开,并且包含的文件也以及被插入到.i文件中,所以当我们无法判断宏定义是否正确或头文件包含是否正确时,可以查看预编译后的文件来确定问题。
二 编译
编译过程就是将预处理完的文件进行一系列词法分析,语法分析,语义分析及优化生成后的汇编代码文件,可以理解为将c语言代码翻译为汇编代码,这个过程较为复杂繁琐,此处简要介绍,具体可以参考《程序员的自我修养》一书,总归而言,该过程相当于指令:
gcc -S hello.c -o hello.s
接下来介绍三个主要步骤
2.1,词法分析
例如,假设有这么一段代码
array[index] = (index+4)*(2+6);
那么编译的时候会怎么做呢,首先该源代码程序被输入扫描器,扫描器的任务就是进行简单的词法分析,把代码中的字符分割成一系列的记号(关键字,标识符,字面量,特殊字符等)。
2.2 语法分析
接下来语法分析器(Grammar Parser)将对由扫描器产生的记号进行语法分析,从而产生语法树(Syntax Tree)。整个分析过程采用了上下文无关语法(Context-free Grammar)的分析手段,如果你对上下文无关语法及下推自动机很熟悉,那么应该很好理解。否则,可以参考一些计算理论的资料,一般都会有很详细的介绍。此处不再赘述。简单地讲,由语法分析器生成的语法树就是以表达式(Expression)为节点的树。我们知道,C语言的一个语句是一个表达式,而复杂的语句是很多表达式的组合。上面例子中的语句就是一个由赋值表达式、加法表达式、乘法表达式、数组表达式、括号表达式组成的复杂语句。它在经过语法分析器以后形成如图所示的语法树。
2.3 语义分析
(转自自我修养一书,该段已经介绍的非常清晰了)
接下来进行的是语义分析,由语义分析器(Semantic Analyzer)来完成。语法分析仅仅是完成了对表达式的语法层面的分析,但是它并不了解这个语句是否真正有意义。比如C语言里面两个指针做乘法运算是没有意义的,但是这个语句在语法上是合法的;比如同样一个指针和一个浮点数做乘法运算是否合法等。编译器所能分析的语义是静态语义(StaticSemantic),所谓静态语义是指在编译期可以确定的语义,与之对应的动态语义(DynamicSemantic)就是只有在运行期才能确定的语义。
静态语义通常包括声明和类型的匹配,类型的转换。比如当一个浮点型的表达式赋值给一个整型的表达式时,其中隐含了一个浮点型到整型转换的过程,语义分析过程中需要完成这个步骤。比如将一个浮点型赋值给一个指针的时候,语义分析程序会发现这个类型不匹配,编译器将会报错。动态语义一般指在运行期出现的语义相关的问题,比如将0作为除数是一个运行期语义错误。
经过语义分析阶段以后,整个语法树的表达式都被标识了类型,如果有些类型需要做隐式转换,语义分析程序会在语法树中插入相应的转换节点。上面描述的语法树在经过语义分析阶段以后成为如图2-4所示的形式。
三 汇编
汇编通过汇编器完成,而汇编器则将汇编代码转化为机器可执行的指令,每一个汇编语句几乎都对应着一个机器指令。就是根据汇编指令和机器指令的对照表一一进行翻译,也不做指令优化。
汇编的指令:
gcc -c test.s -o test.o
以.o为结尾的文件叫做目标文件。
四 链接
链接是一个复杂的过程,链接的时候需要把一堆文件链接在一起才生成可执行程序。
链接过程主要包括:地址和空间分配,符号决议和重定位等这些步骤。
链接解决的是一个项目中多文件、多模块之间互相调用的问题。
这个过程同时依托多个动作执行,即编译过程中实现的符号汇总,汇编过程中实现的符号表的建立,此后在链接步骤时进行符号决议和重定位。更加具体的内容可查阅相关资料。
最后,总体过程如下: