编译原理——一个编译器的各个步骤的介绍

时间:2024-05-31 16:20:49

一个编译器的结构分为分析部分(编译器的前端)和综合部分(编译器的后端)。


编译器的前端:把源程序分解成为多个组成要素,并在这些要素之上加上语法结构。然后,它使用这个结构来创建该源程序的一个中间表示。如检查出源程序没有按照正确的语法构成,或者语义上不一致,它就必须提供有用的信息,使得用户可以按此进行改正,在编译器的前端,还会收集有关源程序的信息,并把信息存放在一个成为符号表的数据结构中。

编译器的后端:根据中间表示和符号表中的信息来构造用户期待的目标程序。

编译原理——一个编译器的各个步骤的介绍

词法分析(lexical analysis):词法分析也称作扫描,是编译器的第一个步骤,词法分析器读入组成源程序的字符流,并且将它们组织成为有意义的词素的序列,对于每一个词素,词法分析器产生如下形式的词法单元作为输出。实际上就是从字符流到单词流的过程。
    词法单元:<token-name,attribute-value>,在这个词法单元中,第一个分量是一个由语法分析步骤使用的抽象符号,而第二个分量指向符号表中关于这个词法单元的条目。符号表条目的信息会被语义分析和代码生成步骤使用。
举个龙书里的例子吧:position=initial+rate*60,在这个赋值语句中的字符可以组成如下词素,并映射成为如下词法单元,这些词法单元会被传递给语法分析阶段。
(1)position是一个词素,被映射成词法单元<id,1>,其中id是表示标识符的抽象符号,而1指向符号表中position对应的条目。一个标识符对应的符号表条目存放该标识符有关的信息,比如它的名字和类型。
(2)赋值符号=是一个词素,被映射成词法单元<=>。因为这个词法单元不需要属性值,所以我们可以忽略它的第二个分量。也可以使用assign这样的抽象符号作为此法单元的名字,
但是为了标记上的方便,我们选择使用词素本身作为抽象符号的名字。
(3)initial是一个词素,被映射成词法单元<id,2>,其中2指向initial对应的符号表条目。
(4)+是一个词素,被映射成词法单元<+>
(5)rate是一个词素,被映射成词法单元<id,3>,其中3指向rate对应的符号表条目。
(6)*是一个词素,被映射成词法单元<*>
(7)60是一个词素,被映射成词法单元<60>,实际上我们应该建立一个形如<number,4>的词法单元,其中4指向符号表中对应整数60的条目。
    单词的分类:标识符,常量,算符,界符(“;”,“,”),关键字

语法分析:也称作解析,语法分析器使用由词法分析器生成的各个词法单元的第一个分量来创建树形的中间表示。该中间表示给出了词法分析产生的词法单元流的语法结构。一个常用的表示方法是语法树,树中的每个内部结点表示一个运算,而该结点的子结点表示该运算的分量。

语义分析:语义分析器使用语法树和符号表中的信息来检查源程序是否和语言定义的语义一致。它同时也收集类型信息,并把这些信息存放在语法树或符号表中,以便在随后的中间代码生成过程中使用。语义分析的一个重要部分是类型检查,编译器检查每个运算符是否具有匹配的运算分量,比如数组下标必须是整数,若用一个浮点数来做下标,编译器就会报错。程序设计语言可能允许某些类型转换,这被称作自动类型转换


中间代码生成:在把一个源程序翻译成目标代码的过程中,一个编译器可能构造出一个或多个中间表示。这些中间表示可以有多种形式。其中语法树是一种中间表示形式。在源程序的语法分析和语义分析完成之后,很多编译器生成一个明确的低级的或类机器语言的中间表示,我们可以把这个表示看作是某个抽象机器的程序。该中间表示应该具有两个重要的性质
易于生成,且能够被轻松地翻译为目标机器上的语言。


代码优化:机器无关的代码优化步骤试图改进中间代码,以便生成更好的目标代码。代码优化又分为中间代码优化和目标代码的优化。


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

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

符号表管理:符号表数据结构为每个变量名字创建了一个记录条目。记录的字段就是名字的各个属性,这个数据结构应该允许编译器迅速查找到每个名字的记录,并向记录中快速存放和获取记录中的数据。
趟(扫描):在一个特定的实现中,多个步骤的活动可以被组合成一趟。每趟读入一个输入文件并产生一个输出文件。比如:前端步骤中的全部步骤可以组合在一起成为一趟。代码优化可以作为一个可选的趟。然后可以有一个特定目标机生成代码的后端趟。 

程序设计语言基础
    1.静态和动态:直接看概念会感觉很绕,所以这里举了个Java的例子帮助理解,在Java类声明中static关键字的使用。
        这里比如声明:public static int x;使得x成为一个类变量,也就是说不管创建了多少个这个类的对象,只存在一个x的拷贝,最重要的是,编译器可以确定内存中的被用于存放整数x的位置,而如果在声明中忽略了“static”,那么这个类的每个对象都会有它自己的用于存放x的位置,编译器没有办法在运行程序之前预先确定所有这些位置
    2.环境和状态:
        环境是一个从名字到存储位置的映射。因为变量就是指内存位置(即C语言中的“左值”),可以换种说法,把环境定义为从名字到变量的映射。
        状态是一个内存位置到它们值得映射。(以C语言得术语来说,即状态把“左值”映射为它们得相应“右值”)
        2.1静态绑定和动态绑定
静态绑定:方法在程序编译期进行绑定
动态绑定:方法在程序运行时根据具体对象的类型进行绑定
大部分的名字到位置的绑定和位置到值的绑定都是动态绑定。
        2.2声明和定义
声明告诉我们事物的类型,而定义告诉我们它们的值。int i就是一个声明,i=1就是一个定义。
        2.3名字、标识符和变量
        标识符是一个字符串,用来指向一个实体,比如一个数据对象、过程、类、或者类型。所有标识符都是名字,但是所有的名字并不都是标识符,比如x.y就是一个受限名字而不是标识符。
变量指向存储中的某个特定的位置
        2.4过程、函数和方法
一个函数通常返回某个类型的值,而一个过程不返回任何值,而面向对象语言,比如Java,C++,通常使用术语“方法”。