?x=a+b×C
x=a+b×50(在最右推倒过程中,每次替换掉的都是产生式最右边的非终结符。这里只做了最右推倒。若将上面的式子,从右向左替换,即为最左归约)。
在描述程序语言的语法结构时,需要借助于上下文无关文法,而文法是描述程序语言的依据。语法分析的方法通常分为两类,即自上而下分析方法和自下而上分析方法。
所谓的自上而下分析方法,就是从文法的开始符号出发,根据文法的规则进行推导,最终推导出给定的句子来。包括递归下降分析法和LL(1)分析法。
递归下降分析法是一种自上而下的分析方法,文法的每个非终结符对应一个递归过程。分析过程就是从文法开始符出发执行一组递归过程,这样向下推导直到推出句子;或者说从根结点出发,自上而下为输入串寻找一个最左匹配序列,建立一棵语法树。
LL(1)分析法又称预测分析法,是一种不带回溯的非递归自上而下分析法。 LL(1)分析法基本思想:
自左向右扫描分析输入符号串从识别符号开始生成句子的最左推导 LL(1):向前看一个输入符号,便能唯一确定产生式 LL(k):向前看k个输入符号,才能唯一确定产生式
自下而上分析法则是从给定的输入串开始,根据文法规则逐步进行归约,直至归约到文法的开始符号为止。
自下而上分析原理是从输入符号串开始,通过重复查找当前句型的“可归约串”并利用有关规则进行规约若能规约为文法的识别符号,则表示分析成功,输入符号串是文法的合法句子,否则有语法错误.
算符优先分析法是一种简单且直观的自上而下分析方法,它适合于程序语言中各类表达式的分析,并且宜于手工实现。所谓算符优先分析,就是依照算术表达式的四则运算过程来进行语法分析,即这种分析方法要预先规定运算符(确切地说是终结符)之间的优先关系和结合性质,然后借助于这种关系来比较相邻运算符的优先级,以确定句型的“可归约串”来进行归约。因此,算符优先分析法不是一种规范归约,在整个归约过程中起决定性作用的是相继两个终结符的优先关系。
LR分析法是一种自下而上进行规范归约的语法分析方法,LR指“自左向右扫描和自下而上进行归约”。LR分析法比递归下降分析法、LL(1)分析法和算符优先分析法对文法的限制要少得多,对大多数用无二义的上下文无关文法描述的语言都可以用LR分析器予以识别,而且速度快,并能准确、及时地指出输入串
的任何语法错误及出错位置。LR分析法的一个主要缺点是,若用手工构造分析器则工作量相当大,因此必须求助于自动产生LR分析器的产生器。 语义分析与中间代码产生
这一阶段的主要工作有两项:首先对语法分析所识别出的各类语法单位进行静态语义审查(如标识符是否定义,类型是否匹配等);若无语义错误,再根据识别出的语法单位的类型进行处理,若是说明语句,则将变量的类型等属性填入符号表,若是可执行语句,则进行初步的翻译,将其翻译为中间代码。
所谓中间代码,是一种含义明确,便于处理的记号系统,通常独立于具体硬件,与现有计算机的指令系统非常相似,比较容易转换成特定计算机的机器指令。常用的中间代码有:三元式、四元式、逆波兰式等。不管用哪种表示形式,其设计原则是容易生成,也容易转换成计算机的机器指令。很多编译程序采用四元式形式的中间代码,其形式为:
(运算符,运算对象1,运算对象2,结果)
语义分析和中间代码生成阶段的工作通常穿插在语法分析过程中完成,因而语义翻译程序通常由一组语义子程序组成。每当分析出一个完整的语法单位,就调用相应的语义子程序执行相应的分析和翻译任务。如当语法分析器分析完var x,y: integer;后,应把x,y的类型integer填入符号表的类型栏中;当分析赋值语句id1:=int1 + id2 * id3 + id2 * id3时,其语义处理过程是:一边读取一边检查result,B ,C,5是否定义,类型是否正确,一边就生成四元式序列,如表1。表中的T1,T2,T3和T4是编译期间引进的临时工作变量。该语句翻译的过程是:首先将表达式翻译为中间代码,再把表达式的值赋值给id1。
表1: 赋值语句id1:=int1 + id2 * id3 + id2 * id3的四元式
序号 四元式 1 2 3 4 5 (*, id2, id3, T1) (+, int1, T1, T2) (*, id2, id3, T3) (+, T2, T3, T4) (:=, T4, _, id1)
经过语义分析分析和中间代码生成阶段后,源程序被加工为整齐和标准形式的中间代码。语义分析依据的是语言的语义规则,表示工具是属性文法、P代码等。
代码优化
代码优化的任务是:产生的中间代码进行等价变换,使生成的目标代码更为高效(时间和空间)。优化的目的主要是提高运行效率,节省存储空间。优化主要有两类,一是与机器有关的优化,主要设计如何分配寄存器,如何选择指令,这类优化是在生成目标代码时进行的;另一类优化与机器无关,主要是对中间代码的优化。
根据优化所涉及的程序范围,优化又分为局部优化,循环优化,全局优化。一个程序从结构上看,作为终点的基本块是其基础。因为基本块的够最简单、因素最单纯,左翼它也是优化的基础,对基本块的优化就是局部优化。循环是程序中要反复执行的部分,优化的效益当然很大,所以循环优化是优化工作的重点。针对整个程序的优化即全局优化,它涉及到对策划能够许数据流分析的问题。
例如,对表1-1的中间代码,在优化阶段,编译程序发现两次计算id2 * id3,就可以省掉第2次的计算,而使用第一次计算的结果。同时因为第5个四元式仅仅把T4赋值给id1,也可以被简化掉。经优化后可变换为表2的四元式。仅剩下了3个四元式,完成了和表1-1同样的功能。
表2 赋值语句id1:=int1 + id2 * id3 + id2 * id3的优化后的四元式
序号 四元式 1 2 3 (*, id2, id3, T1) (+, int1, T1, T2) (+, T2, T1, id1)
代码优化的主要依据是程序的等价变换规则。优化方法包括:公共子表达式的提取、循环优化、删除无用代码等。 目标代码生成
目标代码生成的任务是:把中间代码(或经优化的中间代码)变换成特定机器上的低级语言代码(绝对指令代码、可重定位的指令代码或汇编指令代码)。这一阶段的工作依赖于机器的硬件系统结构和机器指令的含义。工作较复杂,涉及到硬件系统功能部件的运用,机器指令的选择,各种数据类型变量的存储空间分配以及寄存器的分配和调度。
代码生成是指把语法分析后或者优化后的中间代码变换成目标代码,所生成的目标代码一般有如下三种形式:
(1)能够立即执行的机器语言代码,它们通常放在固定的存储区中并可直接
执行,如PC机中后缀为.COM或.EXE的文件。
(2)待装配的机器语言模块,其地址均为相对地址,所以不能直接执行。当
需要执行时由连接装配程序把它们与其他运行程序和库函数连接起来,装配成可执行的机器语言代码,如PC机中后缀为.OBJ的文件。 (3)汇编语言程序,必须通过汇编程序的汇编才可转换成可执行的机器语言
代码如PC机中后缀为.ASM的文件
前面提到的五个阶段的划分是一种典型的划分方式,事实上,并非所有的编译程序都分成这五个阶段,有些编译程序并不生成中间代码,而是直接生成目标代码,有些编译程序不进行代码优化。 表格与表格管理
表格的作用:用来记录源程序的各种信息,以及编译过程中的各种状况。 与编译前三个阶段有关的表格有:符号表、常数表、标号表、分程序入口表、中间代码表等。
(1)符号表:用来登记源程序中的常量名、变量名、数组名、过程名等,记录它们的性质、定义和引用情况。
void main()
{ }
(2)常数表和标号表
表4 常数表
表3 符号表 int m,n,k;
Name m n k Information 整型、变量地址 整型、变量地址 整型、变量地址
值 1 4 表5标号表 Name Information ..... ....... 10 四元式序号4 常数:数值型(整型和实型)、字符型、逻辑型;所有同类型的常数占有一张表格,整型占一张、实型占一张。
标号表:现在用的很少。一般在词法分析时,是不生成四元式的,只记录标号,到了生成目标代码时才在Information处填入内容。 (3)入口名表
作用:登记过程的层号,分程序符号表入口等。
由于程序是由几个过程构成的,过程都是存入内存里,每个过程从哪里
执行?必须知道过程在哪个内存单元中?因此需要登记过程的层号,分程序符号表入口等。 (4)中间代码表
表6 中间代码表
序号 (1) (2) (3) (4) (5) (6) (7) (8) (9) OP = = = (Jump)< + + + Jump return ARG1 i j 1 100 m n k ARG2 k 10 10 1 RESULT m n k (9) m n k (4) 在生成中间代码时才生成该表。 出错处理
任务:如果源程序有错误,编译程序应设法发现错误,并报告给用户。 此过程非常复杂,因为很难发现错误,需要编写出错的处理程序来完成。 错误类型: 可检测的错误和不可检测的错误。
——语法错误:在词法分析和语法分析阶段检测出来;(单词出错、句子不规范) ——语义错误:又分为静态语义错误和动态语义错误。静态语义错误一般在语义分析阶段检测出来,而动态语义错误是在目标程序运行的时候才能查出来;(出现语义上的不可答错误,功能无法实现。如除数为0的错误) ——逻辑错误:不可检测的错误。
而且程序的出错可能出现在任何阶段上,每一步的错误都由“出错处理”来完成。逻辑错误无法用编译程序检测出来,也就不检测此类错误。
例如:while(a||c) //而a||c在循环时判断永远为Ture, }
编译程序的结构
编译程序的五个阶段的功能可分别用五个模块来完成,分别称为词法分析器、语法分析器、语义分析与中间代码生成器、优化器和目标代码生成器。此外,
{
//出现死循环,即为逻辑错误。
......