编译原理基础_龙书学习记录

编译原理基础

语言处理器

编译器是一种程序,可以阅读源程序(某一种语言编写)并将其翻译成目标程序(目标语言编写)。编译器的重要任务之一是报告它在翻译过程中发现的源程序中的错误

解释器是另一种常见的语言处理器。它并不通过翻译的方式生成目标程序。从用户的角度来看,解释器直接利用用户提供的输入执行源程序中指定的操作。

编译器,和解释器(Interpreter) 是常见的两种形式。
主要区别
目标语言翻译成一种能被计算机执行的形式,完成这一过程的软件系统称为编译器(compiler)
解释器并不同编译器一样将源程序编译成目标程序,而是对源语句(结合用户的输入)逐条解释执行。

预处理器
除了编译器之外,创建一个可执行的目标程序还需要一些其他程序,比如将程序员写程序时划分的多个模块聚合在一起的任务,以及将称为宏(micro)展开成源语言格式的任务。(总结:预处理器负责把源程序聚合在一起,并把宏转换为源语言的语句)

汇编器(assembler)
将经过预处理的源程序作为输入传递给一个编译器。编译器可能产生一个汇编语言程序作为其输出,因为汇编语言比较容易输出和调试。接着,这个汇编语言程序由称为汇编器(assembler)的程序进行处理,并产生可重定位的机器代码。(总结:汇编器负责把汇编语言进行处理,并产生可重定位的机器代码

链接器(linker)
大型程序经常被分成多个部分进行编译,因此,可重定位的机器代码有必要和其他可重定位的目标文件以及库文件连接到一起,形成真正在机器上运行的代码。一个文件中的代码可能指向另一个文件中的位置,而链接器(linker)能够解决外部内存地址的问题

加载器(loader)
把所有的可执行目标文件放到内存中执行。

一个编译器的结构

通过编译器,源程序映射到目标语言程序(语义上等价),映射过程大致分为分析综合两部分。

分析(analysis)

  • 把源程序分解为多个组成要素
  • 将组成要素通过语法结构创建该源程序的一个中间表示
  • 检查源程序有没有按照正确的语法结构和语义,如果没有则必须提供有用的信息供用户改正
  • 收集有关源程序的信息存放在一个称为符号表的数据结构
  • 符号表和中间表示形式一起传送给综合部分。

综合(synthesis)
根据分析得到的中间表示符号表中的信息来构造用户期待的目标程序

常称分析部分编译器的前端(front end),而综合部分编译器的后端(back end)

source–>Frontend Optimizer Backend–>Machine Code
编译过程就是把预处理完的文件进行一系列

GCC编译过程相当于如下指令:

gcc -S 源文件 -o 编译文件(****.s) 

有些编译器在前端和后端之间有一个与机器无关的优化步骤。这个优化步骤的目的是在中间表示之上进行转换,以便后端程序能够生成更好的目标程序。如果基于未经过次优化步骤的中间表示来生成代码,则代码的质量会受到影响。因为优化是可选的

Frontend:前端

  • 词法分析
  • 语法分析
  • 语义分析

Backend:后端

  • 生成中间代码
  • 中间代码优化
  • 生成目标代码

词法分析
词法分析(lexical analysis)或称为扫描(scanning)。读入源程序字符流,拆成有意义的词素序列词法单元和一个符号表

运用一种类似于有限状态机(Finite State Machine)的算法
lex程序可以实现词法扫描,它会按照用户之前描述好的词法规则将输入的字符串分割成一个个记号。因为这样一个程序的存在,编译器的开发者就无须为每个编译器开发一个独立的词法扫描器,而是根据需要改变词法规则就可以了。

通常来说,被解析成token的语言是基于上下文无关语法的.

词法分析器会将每个词素生成如下形式的词法单元(token)作为输出:
<token-name, attribute-value>

词法单元生成后会被传送到下一个步骤,即语法分析。

词法单元 <token-name, attribute-value>:第一个分量token-name是一个由语法分析步骤使用的抽象符号,而第二个分量attribute-value指向符号表中关于这个词法单元的条目。符号表条目的信息会被语义分析代码生成步骤使用。
一个标识符对应的符号表条目存放该标识符有关的信息,比如它的名字和类型.
将数字、字符串常量存放到文字表等,以备后面的步棸使用。
分隔词素的空格会被词法分析器忽略掉。

语法分析
语法分析(syntax analysis)或称为解析(parsing)根据词法单元的第一个分量创造一个树形的中间表示,该中间表示给出了词法分析产生的词法单元流的语法结构。一种常用的表示方法时语法树(syntax tree)
树中的每个内部结点表示一个运算,而该结点的子结点表示该运算的分量。

整个分析过程采用了上下文无关的语法(Context-free Grammar)的分析手段。简单得讲,由语法分析器生成的语法树就是以表达式(Expression)为节点的树。(上下文无关文法是递归的一种形式,可以用来指导语法分析)。可以用递归下降的算法来实现。

语法分析器的作用:

  • 根据语法结构,将各个词法单元的第一个分量创建成树形的中间表示,并且将中间表示输出。
  • 中间表示=词法单元流(各个词法单元里的第一个分量)+语法结构。
  • 中间表示一般常用的是语法树。
  • 语法树中每个内部结点表示运算,而该结点的子结点表示该运算的分量。

语义分析
语义分析器(sermantic analyzer)使用语法树符号表中的信息来检查源程序是否和语言定义的语义一致。同时收集类型信息,并把这些信息存放在语法书或符号表中,在随后的中间代码生成过程中使用。

语义分析的一个重要部分是类型检查(type checking)。编译器检查每个运算符是否具有匹配的运算分量。比如,很多程序设计语言的定义中要求一个数组的下表必须是整数。如果用一个浮点数作为数组下标,编译器就必须报告错误。

程序设计语言可能允许某些类型转换,这被称为自动类型转换(coercion)。比如,一个二元算术运算符可以应用一对整数或者一对浮点数。如果这个运算符应用于一个浮点数和一个整数,那么编译器可以把该整数转换(或者说自动类型转换)成为一个浮点数。

中间代码生成
在源程序的语法分析和语义分析完成之后,很多编译器生成一个明确的低级的或类机器语言的中间表示。我们可以把这个表示看作是某个抽象机器的程序。该中间表示应该具有两个重要的性质:

  • 应该易于生成,
  • 能够被轻松地翻译为目标机器上的语言。

将一种称为三地址代码(three-address code)的中间表示形式。这种中间表示由一组类似于汇编语言的指令组成,每个指令具有三个运算分量。每个运算分量都像一个寄存器。

  • 每个三地址赋值指令的右部最多只有一个运算符。因此这些指令确定了运算完成的顺序。在源程序1.1中,乘法应该在加法之前完成。
  • 编译器应该生成一个临时名字以存放一个三地址指令计算得到的值。
  • 有些三地址指令的运算分量的少于三个

代码优化
机器无关的代码优化步骤试图改进中间代码,以便生成更好的目标代码。“更好”通常意味着更快,但是也可能会有其他目标,如更短的或能耗更低的目标代码。

使用一个简单的中间代码生成算法,然后再进行代码优化步骤是生成优质目标代码的一个合理方法

代码生成
代码生成器以源程序的中间表示形式作为输入,并把它映射到目标语言。如果目标语言是机器代码,那么就必须为程序使用的每个变量选择寄存器或内存位置。然后,中间指令被翻译成为能够完成相同任务的机器指令序列。代码生成的一个至关重要的方面是合理分配寄存器以存放变量的值

符号表管理
编译器的重要功能之一是记录源程序中使用的变量的名字,并收集和每个名字的各种属性有关的信息。这些属性可以提供一个名字的存储分配、它的类型、作用域(即在程序的哪些地方可以使用这个名字的值)等信息。对于过程名字,这些信息还包括:它的参数数量和类型、每个参数的传递方法(比如传值或传引用)以及返回类型

符号表数据结构为每个变量名字创建了一个记录条目。记录的字段就是名字的各个属性。这个数据结构应该允许编译器迅速查找到每个名字的记录,并向记录中快速存放和获取记录中的数据。

将多个步骤组合成躺
在一个特定的实现中,多个步骤的活动可以被组合成一趟(pass)。每趟读入一个输入文件并产生一个输出文件。比如,前端步骤中的词法分析、语法分析、语义分析,以及中间代码生成可以被组合在一起成为一趟。代码优化可以作为一个可选的趟。然后可以有一个为特定目标机生成代码的后端趟。

有些编译器集合是围绕一组精心设计的中间表示形式而创建的,这些中间表示形式使得我们可以把特定语言的前端特定目标机的后端相结合。使用这些集合,我们可以把不同的前端和某个目标机的后端结合起来,为不同的源语言建立该目标机上的编译器。类似地,我们可以把一个前端和不同的目标机后端结合,建立针对不同目标机的编译器。

猜你喜欢

转载自blog.csdn.net/u010523811/article/details/124266396