1.什么是编译器,为什么要开发编译器
编译器:将一种程序语言翻译为另一种程序语言的计算机程序。一般来说,源程序为高级语言,而目标语言则是汇编语言或者机器码。
一开始的程序员是用机器码写程序,非常容易出错,后来开始使用汇编语言开始写,但是依旧不高效,后来在50-60年代,有人发明了高级语言及编译器,这就导致写程序就像写数学公式推理一样,方便了很多。所以开发编译器是很重要的。
2.编译器的工作流程
- 对源文件进行扫描,将源文件的字符流拆解成一个个的词(记号),此为词法分析
- 根据语法规则,将这些记号构造成语法树,此为语法分析
- 对语法树各个结点之间的关系进行检查,检查语义规则是否被违背,同时对语法树进行必要的优化,此为语义分析
- 遍历语法树的结点,将各个结点转换成中间代码,并按照特定的顺序拼装起来,此为中间代码生成
- 对中间代码进行优化
- 将中间代码转换成目标代码
- 对目标代码进行优化,生成最终的目标程序
2.1词法分析
编译器扫描源文件的字符流,过滤掉字符流中的空格、注释等,并将其分割成一个个的词(记号、token)。
a = value + sum(5, 123);
将被拆分为11个 token :
a 标识符
= 赋值运算符
value 标识符
+ 加号
sum 标识符
( 左括号
5 整数
, 逗号
123 整数
) 右括号
; 分号
2.2语法分析
词法分析完成后,上面的字符流就被转换为 token 流了:
ID<a> '=' ID<value> '+' ID<sum> '(' NUM<5> ',' NUM<123> ')' ';'
上面的 ID<a> 表示这一个标识符类型的 token ,其内容为 a。其他的赋值符号等全都用‘’
其实不难看出,语法分析其实就是不断拆解一个语句,形成一个语法树。
2.3语义分析
就是在遍历语法树的过程中,遇到变量声明和函数声明时,则将变量名——类型、函数名——返回类型——参数数量及类型等信息保存到符号表里,当遇到使用变量和函数的地方,则根据名称在符号表中查找和检查,查找该名称是否被声明过,该名称的类型是否被正确的使用等等。
其实本质就是一个检查防止错误的过程。
2.4中间代码生成
- 中间代码不仅有一些高级语言的特性,也有接近机器语言的部分,所以相当于一个中间过渡态。这样就可以实现从高级语言代码->中间代码->目标代码,而且这比高级语言代码->目标代码简单很多。
- 增加编译器的模块化、可移植性和可扩展性。一般来说,中间代码既独立于任何高级语言,也独立于任何目标机器架构,这就为开发出适应性广泛的编译器提供了媒介。
如下图中,可以通过编写 m + n 个编译模块而获得 m * n 种编译器。
2.5中间代码优化
编译器对中间代码进行优化,尝试生成体积最小、最快、最有效率的代码。
- 去除永远都不会被执行的代码区
- 去掉未被使用到的变量
- 优化循环体,将每次循环中的运行结果不变的语句移到循环的最外面
- 算术表达式优化,将乘 1 和 加 0 等操作去掉,将乘 2 优化成左移 1 位等
2.6编译过程的错误检查
在词法、语法和语义分析的过程中,都伴随着错误检查,词法错误主要是字符错误(如非法字符、未结束的注释、未结束的字符串等),语法错误主要是格式错误(如语句后未加分号、不匹配的括号等),最常发生的是语义错误(如变量名错误、表达式类型错误、函数参数不匹配等)。编译器不仅检查错误,还需要精确定位出错误发生的位置,协助编程人员修改。