前言
从刚开始接触c的时候开始,编辑器、编译器等概念一直困扰着我,原因大概是直接使用visual C或者Dev-C实现傻瓜式一键开发,源程序转化为可执行文件的过程变成了一个黑盒,限制了我对于计算机编译过程的理解。
这样一种懵懂的状态,在一开始可能并没有太大的不便。当现在随着自己越来越需要开发和理解更深层次的代码,编译过程成为自己必须要理解的内容。
理解编译过程有几个好处
- 理解计算机处理的过程
- 理解各类库的关系
- 便于查找程序编译时的问题
1. 编译整体流程
以编译hello.c为例:
#include <stdio.h>
int main()
{
printf("hello,cs!\n");
}
hello程序的编译过程如下图
st=>start: hello.c
op1=>operation: 预编译器(cpp)---- hello.i 被修改的源程序(文本)
op2=>operation: 编译器(ccl)---- hello.s汇编程序(文本)
op3=>operation: 汇编器(as)---- hello.o 可重定位目标程序(二进制)
op4=>operation: 链接器(ld)---- 链接printf.o和hello.o
i1=>start: printf.o
e=>end: hello
st->op1->op2->op3->op4
op4->e
i1->e
1.1 预处理阶段
预处理器读取头文件的内容,并把它插入到程序文本中。
1.2 编译阶段
编译器(ccl)将文本文件 hello.i 翻译成文本文件hello.s。汇编语言程序中的每条语句都以一种标准的文本格式确切的描述了一条低级机器语言指令。
1.3 汇编阶段
汇编器(as)将hello.s翻译成机器语言指令,把这些指令打包成可重定位目标程序,并保存在目标文件hello.o中,hello.o文件是一个二进制文件。
1.4 链接阶段
hello程序调用了printf函数,printf函数存在于名为printf.o的单独的预编译的目标文件中,链接器(ld)负责处理这种合并。
2.编译器处理流程
在第一节中介绍了程序的整体生命周期,编译作为其中重要的一部分,在本节中进行更具体的分析。
编译将源程序映射为语义上等价的目标程序,主要由两个部分组成:分析部分和综合部分。
分析部分(analysis)将源程序分解为多个组成要素,并在这些要素上加上语法结构。分析部分会收集有关源程序的信息,并存储于一个称为 符号表 的数据结构中。
综合部分(synthesis)根据中间表示和符号表中的信息来构造目标期待的目标程序。分析部分被称为编译的前端(front end),综合部分被称为编译的后端(back end)。
st=>start: 字符流
op1=>operation: 词法分析器
op2=>operation: 语法分析
op3=>operation: 语义分析
op4=>operation: 中间代码生成器
op5=>operation: 机器无关代码优化器
op6=>operation: 代码生成器
op7=>operation: 机器相关代码优化器
e=>end: 目标机器语言
i1=>inputoutput: 符号流
i2=>inputoutput: 语法树
i3=>inputoutput: 语法树
i4=>inputoutput: 中间表示形式
i5=>inputoutput: 中间表示形式
i6=>inputoutput: 目标机器语言
st->op1->i1->op2->i2->op3->i3->op4->i4->op5->i5->op6->i6->op7->e
2.1 词法分析
词法分析器(lexical analysis)读入组成源程序的字符流,并且将它们组织成有意义的词素。生成符号表。
<token-name, attribute-value>
2.2 语法分析
语法分析器(syntax analysis)使用由词法分析器生成的各个词法单元的第一个分量来创建树形的中间表示。一个常用的表示方法是语法树。
2.3 语义分析
语义分析器(semantic analyzer)使用语法树和符号表中的信息来检查源程序是否和语言定义的语义一致。同时收集类型信息,并将这些信息存放在语法树或符号表中,以便在随后的中间代码生成过程中使用。
2.4 中间代码生成
2.5 代码优化器
机器无关的代码优化步骤试图改进中间代码,以便生成更好的目标代码。通常而言,“更好”意味这更快,但也可能是“更短”或“能耗更低”的目标代码。
2.6 代码生成器
代码生成器以源程序的中间表示形式作为输入,并把它映射到目标语言。