标签:编译原理
编译器(compiler):阅读以某一种语言(源语言)编写的程序,并把该程序翻译成为一个等价的、用另一种语言(目标语言)编写的程序。编译器的重要任务之一是报告它在翻译过程中发现的源程序中的错误。
解释器(interpreter):另一种常见的语言处理器。它并不通过翻译的方式生成目标程序。从用户的角度看,解释器直接利用用户提供的输入执行源程序中指定的操作。
在把用户输入映射成输出的过程中,由一个编译器产生的机器语言目标程序通常比一个解释器快很多。然而,解释器的错误诊断效果通常比编译器好,因为它逐个语句地执行源程序。
一个源程序可能被分割成多个模块,并存放在独立的文件中。把源程序聚合在一起的任务有时会由一个被称为预处理器(preprocessor)的程序独立完成。预处理器还负责把那些成为宏的缩写形式转换为源语言的语句。
然后,将经过预处理的源程序作为输入传递给一个编译器。编译器可能产生一个汇编语言程序作为其输出,因为汇编语言比较容易输出和调试。接着,这个汇编语言程序由称为汇编器(assembler)的程序进行处理,并生成可重定位的机器代码。
大型程序经常被分成多个部分进行编译,因此,可重定位的机器代码有必要和其他可重定位的目标文件以及库文件连接到一起,形成真正在机器上运行的代码。一个文件中的代码可能指向另一个文件中的位置,而链接器(linker)能够解决外部内存地址的问题。最后,加载器(loader)把所有的可执行目标文件放到内存中执行。
分析(analysis)部分部分把源程序分解成为多个组成要素,并在这些要素之上加上语法结构。然后它使用这个结构来创建该源程序的一个中间表示。分析部分会收集有关源程序的信息,并把信息存放在一个成为**符号表(symbol table)的数据结构中。符号表和中间表示形式一起传送给综合部分。
综合(synthesis)部分根据中间表示和符号表中的信息来构造用户期待的目标程序。分析部分经常被称为编译器的前端(front end),而综合部分被称为后端(back end)。
编译过程顺序执行了一组步骤(phase)。每个步骤把源程序的一种表示形式转换成另一种表示形式。一个典型的把编译程序分解成为多个步骤的方式如下图所示。在实践中,多个不走可能被组合在一起,而这些组合在一起的中间步骤之间的中间表示不需要明确地构造出来。存放整个源程序的信息的符号表可由编译器的各个步骤使用。
编译器的第一个步骤成为词法分析(lexical analysis)或扫描(scanning)。词法分析器读物组成源程序的字符流,并且将它们组织成为有意义的词素(lexeme)的序列。对于每个词素,词法分析器产生如下形式的词法单元(token)作为输出:
<token-name, attribute-value>
第一个分量token-name是一个由词法分析步骤使用的抽象符号,而二个分量attribute-value指向符号表中关于这个词法单元的条目。符号表条目的信息会被语义分析和代码生成步骤使用。
比如,赋值语句:
position = initial + rate * 60
经过词法分析之后被表示成如下的词法单元序列
<id, 1> < = > <id, 2> < + > <id, 3> < * > <60>
编译器的第二个步骤称为语法分析(syntax analysis)或解析(parsing)。语法分析器使用由词法分析器生成的各个词法单元的第一个分量来创建树形的中间表示。该中间表示给出了词法分析产生的词法单元流的语法结构。一个常用的表示方法是语法树(syntax tree),树中的每个内部结点表示一个运算,而该结点的子节点表示该运算的分量。
语义分析器(semantic analyzer)使用语法树和符号表中的信息来检查源程序是否和语言定义的语意一致。它同时也收集类型信息,并把这些信息存放在语法树或符号表中,以便在随后的中间代码生成过程中使用。
语意分析的一个重要部分是类型检查(type checking)。编译器检查每个运算符是否具有匹配的运算分量。
程序设计语言可能允许某些类型转换,这被称为自动类型转换(coercion)。
在把一个源程序翻译成目标代码的过程中,一个编译器可能构造出一个或多个中间表示。这些中间表示可以有多种形式。语法树是一种中间表示形式,它们通常在语法分析和语义分析中使用。该中间表示应该具有两个重要的性质:它应该易于生成,且能够被轻松地翻译为目标机器上的语言。
机器无关的代码优化步骤试图改进中间代码,以便更好的生成目标代码。“更好”通常意味着更快,但是也有可能会有其他目标,如更短的或能耗更低的目标代码。
代码生成器以源程序的中间表示形式作为输入,并把它映射到目标语言。如果目标语言是机器代码,那么就必须为程序使用的每个变量选择寄存器或内存位置。然后,中间指令被翻译成为能够完成相同任务的机器指令序列。
编译器的重要功能之一是记录源程序中使用的变量的名字,并收集和每个名字的各种属性有关的信息。这些属性可以提供一个名字和存储分配、它的类型、作用域等信息。对于过程名字,这些信息还包括:它的参数数量和类型、每个参数的传递方法(比如传值或传引用)以及返回类型。
符号表数据结构为每个变量名字创建了一个记录条目。记录的字段就是名字的各个属性。这个数据结构应该允许编译器迅速查找到每个名字的记录,并向记录中快速存放和获取记录中的数据。
前面关于步骤的讨论讲的是一个编译器的逻辑组成方式。在一个特定的实现中,多个步骤的活动可以被组合成一趟(pass)。每趟读入一个输入文件并产生一个输出文件。
一些常用的编译器构造工具包括:
x
的声明的作用域是一段上下文,在此上下文中对x
的使用指向声明。如果仅仅通过阅读某个语言的程序就可以确定其作用域,那么这个语言就使用了静态作用域,或者说词法作用域。否则这个语言就使用了动态作用域。x
的声明D
,而嵌套于这个块中的块B
中有一个对名字x
的使用。如果这两个块之间没有其他声明了x
的块,那么这个x
的使用位于D
的作用域内。标签:编译原理
原文地址:http://blog.csdn.net/foreverling/article/details/43890311