编译原理演示文稿7.ppt
《编译原理演示文稿7.ppt》由会员分享,可在线阅读,更多相关《编译原理演示文稿7.ppt(117页珍藏版)》请在淘文阁 - 分享文档赚钱的网站上搜索。
1、 第七章 编译程序7.1 编译程序考虑的因素 编译程序设计时,除了需用到前介绍的分析技术和制导翻译技术外,还要考虑如何从源程序数据空间映射到具体物理存储空间,也就是运行时的数据表示。在运行时如何组织或存放数据、在源程序中同名标识符是怎样描述不同的对象、运行时的程序控制权是如何转移和参数是如何传递的以及如何生成质量较高的目标代码都是编译程序设计者需考虑的问题。7.1.1 数据类型 类型的合法性检查是判断数据的类型是否与上下文的要求相一致,例如Pascal的运算符+不能作用在字符型数据上,而C语言的+却能作用在字符型数据上。在数据类型上定义的各种运算通常包括赋值和一系列类型转换规则,这些规则保证了
2、作用在数据对象上的某个运算符顺便通过由编译程序的类型的合法性检查,并实现其合法的算和赋值。因此,给出定义:定义定义7.1 数据类型是对该类型数据(变量或常量)的取值是否合法以及对该类型据的运算是否合法的一种说明。实现和完成数据类型的合法性检查,它包括以下任务:(1)检查运算符作用在运算对象上的合法性,这一合法性保证了该运算能产生正确的运算结果。(2)根据程序设计语言运算符的类型转换规则,将一种类型数据转换成另一种数据类型。(3)能够使用相应的目标机器指令实现这种在上述类型上定义的运算。例:设有Pascal程序段var a,b:integer;x:real;begin read(a);b:=10
3、;x:=a mod b;a:=ax*10end;对于读语句read(a)和赋值语句b:=10都满足简单的类型检查。在赋值语句x:=a mod b中虽然a mod b的结果是整型的,但仍能满足将a mod b的结果赋给实型变量x。这是因为在Pascal中定义了将整型转换成实型的转换规则,因而编译程序需生成将a mod b的结果转换成实型的指令代码。而对于语句a:=ax*10,虽然通过Pascal定义的类型规则可以将a转换成实型,求出ax*10的结果类型为实型,但Pascal不允许将实型赋给整型,则出错。例:设有C语言程序段int a,b;real x;scanf(“%d”,&a);b=10;x:
4、=a/b;a:=ax*10;可以看出,上述二个程序段期望完成的功能是一样的,但前者不能通过编译,而后者能顺利通过编译的类型检查,这是因为C语言中赋值语句a:=ax*10中也包含了强制将实型转换成整型。根据语言的类型定义方式,可以将类型分为基本类型和构造类型,基本类型是指系统已定义的数据类型,如C语言中的整型、浮点型(实型)、字符型。构造类型的指通过基本类型或已定义的类型构造出的新的数据类型,如Pascal中的数组、记录和集合。引进了构造类型后,类型的合法性检查变得复杂。其检查方法有二大类,一类是名字等价,另一类是结构等价。所谓名字等价也就是如果二个类型是等价的,当且仅当二个类型的名字或与类型名
5、字的别名是等价的。例:设有Pascal程序段type int=integer;var a:integer;b:integer;c:int;a和b是同一类型名integer故它们是等价的;虽然a和c的类型名不同,但是int是integer的一种别名,故a和c的类型还是等价的。所谓结构等价也就是如果二个类型是等价的,当且仅当二个类型具有相同的类型表达式。定义定义7.2 类型表达式是递归定义的:(1)类型表达式是基本数据类型(2)类型表达式是由数组、记录、集合、指针、函数等作用在类型表达式上的类型。检查类型的名字等价相对简单,只要为定义的类型名建立一张符号表,通过查表就可以判定二个类型是否名字等价。
6、虽然,对于类型的等价的直观概念是结构等价,但结构等价检查的实现方法稍复杂。需为每个类型建立表示类型的结构树或无环有向图,如图为类型。record name:array1.20 of char;age:integerend;的树结构表示。其中,array中的integer表示下标的类型对于如说明链表或树的数据结构的定义时,需递归定义。因此递归定义的类型图为无环有向图。图为类型type link=node;node=record name:array1.20 of char;next:linkend;的无环有向图。检查二个类型是结构等价,只要二个类型结构树或无环有向图相等即可。检查类型等价也分成静
7、态检查和动态检查。由编译程序能完成的类型检查叫做静态类型检查;由目标程序运行时所作的类型检查就称为动态类型检查。一般地,如果要在生成的目标代码中完成类型检查,则目标代中不但要保存数据的值,而且还保存该数据的类型,则可完工成相应的动态类型检查。因算法语言的类型检查多数是静态的类型检查,在这里仅介绍了静态的类型检查。7.1.2 数据结构 一种程序设计语言如允许使用的数组、记录、集合、字符串、表、栈等形式的数据结构,在编译程序中应为它们提供相应的翻译。为了能对这些数据结构中的元素的引用,编译程序必须完成从这些数据的逻辑结构到访问这些数据元素的物理结构的映射。使用上述数据结构应考虑:(1)映射算法相对
8、简单,根据逻辑结构容易计算出物理地址。(2)从逻辑结构投影到物理结构时,不至于越界或存储溢出。(3)使用的数据结构承担这种程序设计语言的主要功能。(4)在这些数据结构上定义的运算。例:设有类Pascal程序段program example(input,output);type student=record no:integer;name:array1.10 of char;score:integer end;weekday=(sun,mon,tue,wed,thu,fri,sat);var st:array1.50 of student;day:weekday;i,j:integer;begi
9、n today:=sun;i:=1;sti.no:=30;sti.name:=wang fang end.从上可以看出,st是一个记录型的数组,它首先通过数组的映射计算出sti的地址,然后再通过记录的映射分别计算出sti.no 和 sti.name的地址。对于枚举类型的数据sun,mon,tue,wed,thu,fri,sat如何描述它们的值和在枚举类型的数据上的运算。对于字符串sti.name应考虑是否允许整体赋值和字符串是否允许连接等运算,运算后的结果到存储器中。连接后的结果如超出字符串设定的长度,如何强制或报错。如要实现一个“栈”的数据类型,就应该考虑是否设置栈的最大容量,栈上的运算如:
10、push和pop。栈元素所允许的数据类型,如栈元素的类型允许是数组、记录、集合、字符串,但不能是表或栈。还要考虑如何将栈顶映射的物理存储空间。如设定静态的定长的栈空间用指针或下标指示当前栈顶或设定不定长的动态空间当入栈时申请存储空间,出栈时返回存储空间。7.1.3 作用域规则 一个程序设计语言的作用域规则确定了该程序设计语言的某个程序的不同程序块中所说明的标识符的可访问性。从另一个角度来看,在程序中当访问一个标识符通过作用域规则可确定究竟访问的是哪一个实体中说明的标识符。一般来说一个程序设计语言的一个标识符或数据项的作用域是在说明该标识符或数据项的程序块内,如Pascal中的标识符的作用域规则
11、,叙述如下:(1)一个标识符的作用域是从该标识符的定义点开始至定义该标识符的分程序结束为止,包含在这个分程序中的所有内分程序,并遵循规则2。(2)当一个标识符x在分程序A中被定义,在A所包含的分程序B中又重新被定义,则分程序B以及包含在B中的所有分所出的x不再是A中定义的x,而是B中定义的x。例:设有Pascal程序段program example(input,output);var x,y:real;procedure pro;var y,z:integer;begin x:=y;endbegin end.在过程pro中赋值语句x:=y的x为主程序中说明的x,而y为过程序中说明的y。作用域还
12、包括文件作用域,如C语言的函数作用域是从说明点开始,一直延伸到源文件结束。作用域规则的不同直接影响到编译程序需生成的代码。作用域规则分为静态作用域和动态作用域二种。静态作用域是指当标识符在本分程序中没有说明时,到说明该分程序的上一分程序中找相应的标识符,依次类推直至找到该标识符或整个上层分程序中均没找到该标识符为止。否则出错。动态作用域是指当标识符在本分程序逻辑中没有说明时,到调用该分程序的程序中找,依次类推直至找到该标识符或整个调用程序中均没找到该标识符为止,同样动态作用域也分为找到和出错。显然上述Pascal中的作用域规则是静态作用域规则,静态作用域规则在编译中处理要比动态作用域的处理简单
13、些。7.1.4 控制结构 一种程序设计语言的控制结构是该语言在程序运行期间用于改变控制流的语言特征集合。它包括有条件控制转移,条件执行、循环控制、过程序调用、转移和出口。编译程序在翻译时必须保证源程序不能违法控制结构的语义。如Pascal中只能从循环体内转向循环体外、C语言中不能从一个函数转向另一个函数、BASIC中不能在循环体内修改循环变量的值,而C没有这种限制。例:错误的控制结构begin for i:=1 to 10 begin label:x:=x+1;end;goto label end程序的控制结构实际是程序执行权的转移。为了减少程序员编制的源程序中的错误。通常对程序的结构一定的约
14、定。因此,在设计或翻译程序设计语言的的控制结构时,需考虑:设定或限制某种程序的控制,应满足程序设计的方法学或结构化程序设计的思想。对每一种程序的控制结构,均能用编译的翻译技术来实现翻译。提供程序的控制结构有利于程序员实现程序和查找源程序的错误。综上所述,如何实现控制结构的翻译也是编译程序必须考虑的问题。7.2 运行时的内存分配 编译程序需要为常量或变量等数据分配运行时的存储空间。编译程序从操作系统中申请编译程序计算出的所需的内存或者编译程序生成在运时需申请内存的指令。内存分配包括以下3个任务:(1)确定用来表示某一数据项的内存的大小。(2)使用适当的内存分配策略实现具体数据的作用域和生命期。(
15、3)确定以适当的算法访问生命期内的数据,包括基本型数据构造型数据。以上三个任务实际上是完成逻辑数据到具体数据的映射。根据内存分配的时机,分为静态存储分配和动态存储分配方式。采用哪一种分配策略是根据源语言的结构特点、源语言的数据类型、源语言中的作用域规则,源语言存储管理和组织的方式的复杂度都会影响到存储空间的分配策略。下面就存储分配的主要技术,进行分析和讨论。7.2.1 静态和动态内存分配 为目标程序分配运行时所需的存储空间,一种是在目标程序运行前,由编译程序为数据分配存储空间,在程序的运行期间不再分配和解除这种内存分配。另一种在程序运行期间均可以对内存实现分配或解除分配,一旦存储分配解除该存储
16、空间内的数据便失去意义。前者称为静态存储分配,后者称为动态存储分配。定义7.3 内存结合是指数据项的逻辑地址映射到内存物理地址的联系 内存分配和内存结合是两个不同的概念,内存分配相当于在上网时用户申请了用户名,此时网络营运商将某个用户名“分配”给了该用户,但此时该用户不一定在上网。内存结合相当于网络用户用了申请到的用户名上网。网络用户的结合从用户的上网起至该用户下网止。内存分配是实现内存结合的过程和手段,内存结合在内存解除分配时终止。源程序数据项的结合时刻决定编译程序的处理方式。当编译程序可以产生内存分配的代码时,不要在运行产生这种分配。这会影响到目标程序的效率。由于内存分配的时机不同,引入静
17、态存储分配和动态存储分配的两种方式。1.静态存储分配 在静态存储分配中,编译程序在运行前为数据分配内存。典型的静态存储分配是在编译时分配的。这要求编译程序在编译时能确定目标程序运行中据需的全部数据空间的大小,编译程序将数据和内存结合从而实现存储分配。编译程序一旦实现静态存储分配,在整个程序运行期间不再进行存储分配或解除存储分配的工作直到整个程序运行结束。如C语言中的全局变量和静态变量都是静态存储分配的。例:图说明了下列C语言程序段的静态数据存储分配int a,b,c;char d100;int f();main()int x=5,y=8;printf(“%d”,f(x);)printf(“%d
18、”,g(y);)int f(int n)static int p=0;p+=n;return(p);int g(int m)static float q=0;q+=m;return(q);图中的阴影部分表示其它数据的存储空间。静态存储分配的限制:(1)数据对象的长度和它在内存中的位置在编译时都是已知的(2)不能建立如递归过程或递归函数中的一个名字对应多个存储空间的结合(3)不能建立数据对象长度不定的存储空间2.动态存储分配 为了避免上述对静态存储分配的限制,引进了动态存储分配。动态分配的数据区在运行不是一成不变的,它有时引入,有时退出,是一个动态的变化过程。动态存储分配分为二大类,一类是随着程
19、序单元的进入而分配,随着程序单元的退出而撤销。另一类是由程序控制其分配和撤销。它们分别用栈和堆来实现,被称为栈式动态存储分配和堆式动态存储分配。a)栈式动态分配 当一个程序设计语言允许递归时,则每次进入该递归过程或函数时,就应分配一块大小相同的内存。当该过程或函数结束时相应的内存释放。由于递归过程或函数的进入和退出符合栈的先进后出的原则,故这一类存储分配可用栈实现。把每次进入过程或函数需分配的数据区称为活动记录。活动记录包括(如图所示):(1)连接数据 a)老的SP。每个活动记录,有一个基址指针称为SP。为了撤销本活动记录时能指出上一个调用者的活动记录就应保存调用者的SP称为老的SP。而老的栈
20、顶不必保存,因为老的栈顶为现SP1(设SP占一个存储单元)b)返回地址 返回地址是指子程序完成后的返回地址(对于函数还要返回其函数值)。(2)程序状态字 为保存调用者的机器信息,如程序计数器、标志寄存器和寄存器的值,在返回时,预以恢复。(3)参数个数(4)形式单元 存放实在参数的值或地址。(5)局部数据 局部数据包括:局部变量、静态数组的数据区、动态数组的内情向量和临时工作单元。例:设有程序结构如下所示:program main;全局变量或数组说明;proc R;end R;proc Q;end Q;在采用栈式动态分配时,因每进入一个过程就为该过程或函数分配存储空间。则图(a)表示主程序中调用
21、了R;在R中又调用了Q的存储结构。图(b)表示在Q中又调用了Q的存储结构。图(c)表示在Q退出,又调用了R的存储结构。图(d)表示在R退出的存储结构。图(e)表示在Q退出的存储结构。图(f)表示在R退出的存储结构。经过讨论了栈的存储分配,还要考虑过程式或函数调用和返回需完成的工作。前面第六章说过过程调用的四元式序列是:par T1par T2par T3par Tncall P,n现考虑四元式par和call是如何执行的,也就是par和call要完成怎样的工作。对于每个par Ti 需产生把参数填入活动记录的指令。由于SP、返回地址、参数个数和程序状态字的所需的字节数对于某个过程来说是不变的且
22、可以计算出的,因此,每个参数存放的地址(相对于SP的地址)也是可以计算出的,设其和为m,则对于par Ti可翻译成类似于如下指令:TOP+i+m:=Ti这里TOP为老的TOP,每个参数均只占一个单元,Ti为参数的值或参数的地址。四元式call P,n应翻译成:TOP+1:=SP /*保护现行的SP*/TOP+i+1:=当前寄存器值 /*保护现场*/TOP+m1:=n /*保存参数个数*/call P进入过程P后,应赋新的SP,保护返回地址(返回地址也可以由指令call P保存系统的栈中),定义新的TOP值。即执行指令:SP:=TOP+1SP+1:=返回地址TOP:=TOP+L /*L为P过程所
23、需的单元数*/当执行到过程、函数的返回语句或过程、函数中的语句全部被执行完毕应返回调用者。其工作为计算出内存出栈后的TOP、将复原老的SP,对于函数还应将函数值存放到指定的连接单元中,然后执行返子指令:TOP:=SP 1SP:=SP+0/*如返回的是函数则执行一条将函数值保存到指定单元或寄存中*/RET /*返回调用程序*/在这里SP、TOP、参数等均只占一个单元,若它们占用不等长的单元在编译中也是可以计算出的,其实现方法类似。b)堆式动态存储分配当一种程序设计语言允许用类似于Pascal中new和dispose语句申请和释放内存,则不能用栈式存储分配,这因为其申请和释放的次序不满足先进后出的
24、条件。这种数据只能借助于一种称为堆式动态存储分配的方法来实现。假定程序运行时有一个较大的存储空间,当用户申请堆内存时,就分配一块,不用时返回。由于分配和返回的时间没有规律可循。当程序运行一段时间后,原来一块较大的存储空间很可能分成很多很多块。然而当需要长度为N的存储空间只能寻找比N大或相等的内存块分配,以此往复。最后在存储器池中出现了很多碎片。这些碎片就很难再为程序其它部分所用。为了解决碎片问题,一种方法是通过移动将碎片合并在一起,但这种方法是低效的,移动需化费很多机时。另一种方法是将原较大的存储空间分成若干等长的块,申请时分配以块为单位不足一块的长度也分配一块,返回时则必定也是以块为单位的。
25、这样解决了碎片问题,即外零头问题。但以块为单位分配浪费了块内的存储空间,也就是生成了内零头。这种方法虽然在分配时没有完全利用全部存储空间,但提高了系统的效率。由于操作系统也需要管理内存,在编译实现时直接可以利用操作系统的内存管理,即需要内存时,向操作系统申请内存,释放时直接将内存返回操作系统。7.2.2 嵌套结构的内存分配 前面介绍的栈式动态存储分配,每个过程或函数只能使用局部变量不能使用非局部变量,这与Pascal语言结构的特点是不同的。Pascal允许过程嵌套定义,也就是在过程或函数内需要使用非局部变量。下面是有嵌套过的Pascal程序。program sort(input,output)
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 编译 原理 演示 文稿
限制150内