1、编译原理Compiler Theory,第八章 运行时的存储空间组织,8.1 概述8.2 静态存储分配8.3 动态存储分配,第8章 运行时的存储空间组织,2,8.1 概述,编译程序的最终目的是将源程序翻译成等价的目标程序。因此,除了进行词法、语法、语义分析外,在生成目标代码前,需要把程序静态的正文与程序运行时的活动联系起来,弄清楚在代码运行时刻,程序中的各种变量、常量等是如何存放的,如何去访问它们。,3,编译程序必须分配目标程序运行时的存储空间。这些存储空间包括:用户定义的各类变量和常量所需存储单元;作为保留中间结果的参数传递用的临时工作单元;调用过程或函数时需要的形式单元;返回地址以及组织输
2、入/输出所需的缓冲区。,4,数据空间的使用和管理方法,从本质上看,数据对象的空间分配是将程序中的每个对象名字与一个存储位置进行绑定。目标程序运行时的数据空间组织的基本依据是程序设计语言对程序运行中存储空间的使用和管理办法的规定。如:递归调用的过程,在运行时,其同一个局部名字应对应不同的运行空间位置。,5,存储空间组织必须考虑的问题,过程是否允许递归?当控制从一个过程的活动返回时,对局部如何处理?过程是否允许引用非局部名?过程调用时如何传递参数;过程是否可以作为参数被传递和作为结果被返回?存储空间可否在程序控制下进行动态分配?存储空间是否必须显式地释放?,6,存储空间分配方法,静态存储分配在编译
3、阶段进行的存储分配。对程序中的简单变量和常量数组,由于它们所需的存储单元数在编译时就可以确定,因此在编译时就可以给它们分配存储单元。动态存储分配在运行阶段才能确定数据的位置及大小的分配方法。,7,数据区,静态数据区由静态存储分配产生的数据区。这种数据区在整个程序运行过程中是固定不变的。动态数据区由动态存储分配产生的数据区。这种数据区不是固定不变的。随着相应程序单位的调用和返回,它也会进入和退出。,8,8.2 静态存储分配,静态存储分配对程序设计语言的要求:不允许有递归调用;不允许有嵌套的子程序;不允许有可变数组。FORTRAN语言满足这些要求。,9,局部数据区的安排,变量地址= 相对地址 +
4、起始地址,10,8.2 动态存储分配,很多高级语言都支持嵌套分程序、嵌套或递归过程结构及动态数组,这些需要运行时存储空间管理机制。栈式存储分配方法是适合于这类语言的一种存储管理方法。栈式存储分配方法是把整个程序的存储空间都安排在一个栈内。,11,栈式存储分配基本思路,进入主程序时,将主程序定义的各类全程量所需存储空间分配于栈顶;每当调用一个子程序(过程/函数)时,就将其所需的存储空间分配于栈的当前栈顶;每当从子程序运行结束时,就从栈中释放其所占空间;整个程序执行完后,释放它所占用的全部空间。,12,栈式存储分配适合于:层次嵌套结构允许过程递归调用含可变数组的程序设计语言。,13,1.非嵌套结构
5、、不含可变数组但允许递归调用的存储空间分配,数据区内容,将这样一个数据区称为一个活动记录。活动记录的大小在编译时可以静态地确定。,14,说明,活动数据区的内容与静态存储分配的数据区相同,所不同的是要允许递归,则每调用一次过程都要生成一个数据区,每次操作都是在新的数据区上进行,递归调用多少次就生成多少个数据区由于只有在运行时才知道调用多少次,因此只能在运行时确定分配多少空间。,15,16,示例,Program main; 全局变量 procedure R; end;(R) procedure Q; call R; end;(Q) 主程序; call Q; end.(main),设在主程序中调用Q
6、,Q又调用了R,则栈式分配为:,每调用一个过程就开辟一个数据区。当某个过程执行完后,退出此过程的数据区,进入下一个过程的数据区。,栈式分配的过程,运行时,每当进入一个过程就将此过程的数据区(活动记录)置于栈顶。当过程执行完毕后,该过程所对应的数据区也不复存在,进入到下一个将要执行的过程的数据区中。因此,有必要用两个寄存器来记下数据区的大小。,top,sp,活动记录内容,现行过程活动记录起点,17,老sp,sp,top,top,sp,当Q退出后,SP应指到什么地方不知道(因为R的长度不知),因此在每个数据区设一老SP记下上一过程的SP值。,18,设a为数据区中的相对地址,则asp为相对地址a+s
7、p的地址;,sp,20,则20sp:将20单元的简单变量取出来。,19,示例,过程调用四元式:(par,_,_,T1)(par,_,_,T2)(par,_,_,T3)(call,_,_,p),/地址调用 (i+3)top:=add(Ti) /值调用 (i+3)top:=Ti,对应的目标动作:进入P前的动作:,1top:=sp;/保存老sp3top:=参数个数,进入P后,首先应定义新的活动记录的sp,然后保存返回地址并定义新的top值,sp,top,老sp,参数个数:3,Add(T1),Add(T2),Add(T3),20,示例(续1),sp:=top+1; /定义新sp,对应的目标动作:进入P
8、后的动作:,过程P的活动记录长度,在编译时可静态计算出来。,sp,top,sp,top,1sp:=返回地址;/保存返回地址,top:=top+L; /定义新的top,返回地址,21,示例(续2),对应的目标动作:从P返回时的动作: top:=sp-1;,sp,top,sp,top,sp:=0sp; /老sp,无条件转2top /返回地址,22,说明,动态存储分配比静态存储分配省空间。因为当某一过程执行完后就可以释放它所占的空间,只有当都不释放空间时才与静态分配所用空间相同。但动态分配省空间所付出的代价是运行时分配,降低了执行效率。,23,示例:递归调用的存储空间分配,某含递归调用的C语言程序:
9、 printd(int n) int i; if(n0) putchar(-); n=-n; if(i=n/10)!=0) printd(i); L:putchar(n%10 +0); /打印余数在主程序中调用:printd(-125),24,25,存储分配过程1,进入子过程体前: (1+3)top=Ti=n=-125 1top=sp=k /保留老sp 3top=参数个数=1,sp,top,sp,top,参数个数:1,返回地址,形参:n=-125,简单变量:i=12,老sp:K,k1,转入子过程体 sp=top+1 1sp=返回地址 top=top+L,进入子过程体内,填简单变量i 参数:n=
10、-1250,先打印-,n=125; i=125/10=12!=0 : printd(12),printd(int n) int i; if(n0) putchar(-); n=-n; if(i=n/10)!=0) printd(i); L:putchar(n%10+0); ,形参:n=125,26,存储分配过程2,进入子过程体前: (1+3)top=Ti=n=12 1top=sp=k1 3top=参数个数=1,sp,top,转入子过程体 sp=top+1 1sp=返回地址 top=top+L,进入子过程体内,填简单变量i i=12/10=1!=0 : printd(1),printd(int
11、n) int i; if(n0) putchar(-); n=-n; if(i=n/10)!=0) printd(i); L:putchar(n%10+0); ,老sp:k1,返回地址,形参:n=12,k2,参数个数:1,sp,top,27,存储分配过程3,进入过程体内: 参数n=1 i=1/10=0; putchar打印:1%10+0=1;,printd(int n) int i; if(n0) putchar(-); n=-n; if(i=n/10)!=0) printd(i); L:putchar(n%10+0); ,28,存储分配过程4,返回到第2次调用处(包括返回数据区): n=12
12、,打印:12%10+0=2,printd(int n) int i; if(n0) putchar(-); n=-n; if(i=n/10)!=0) printd(i); L:putchar(n%10+0); ,sp,top,简单变量:i=1,老sp:k1,返回地址,形参:n=12,k2,参数个数:1,top,sp,29,存储分配过程5,返回到第1次调用处(包括返回数据区): n=125, 打印:125%10+0=5 总的打印结果为:-125,printd(int n) int i; if(n0) putchar(-); n=-n; if(i=n/10)!=0) printd(i); L:pu
13、tchar(n%10+0); ,top,sp,sp,top,2.非层次嵌套、允许递归且含可变数组的程序设计语言的存储分配,原来的活动记录,若数组长度可变,则不能确定活动记录的大小,但要求在编译时一定要能确定活动记录的长度,为此,将活动记录改为:,新的活动记录,活动记录,30,内情向量,对数组建立内情向量,记录已知信息。如:,这样便于计算数组越界,31,示例,Void p() int A1.m; int B1.n; ,sp,top,32,过程内数组说明符对应的目标动作,计算各维的长度(或上、下限)调用数组空间分配子程序,其参数为各维的长度(或上、下限)及内情向量的起始地址。,33,数组空间分配子
14、程序功能,填写内情向量中的信息 (数组起址:=top+1 )调整top:top:=top+v v为数组体积,top,sp,top,34,3.过程嵌套结构、允许递归调用、含可变数组的程序设计语言的存储分配,例: procedure P procedure Q procedure R end end end,这种过程嵌套结构所附带的语义检查:过程调用的合理性变量使用的合法性,35,说明,由于过程定义是嵌套的,任一个过程都可以引用其外层过程中定义的变量。因此,子过程必须要知道其全部外层过程的最新活动记录的地址。由于允许递归调用和包含可变数组,因此过程的活动记录位置往往是变化的,因此应跟踪每个外层过程
15、的最新活动记录的位置。,36,常用的跟踪方法,静态链指向其直接外层过程的最新活动记录的基地址。Display表每当进入一个子过程时,在建立它的活动记录区的同时建立一张嵌套的层次显示表display。,37,Program P; Var a,x: integer; Procedure Q(b:int ) Var i:integer; Procedure R( u:int; Var v:int); Var c,d: integer begin if u=1 then R(u+1,v) V:=(a+c)*(b-d); end R begin R(1,x); endQ procedure S; var
16、 c,i: integer; begin a:=1; Q(c); end Sbegin a:=0; S; end,层次,0,1,2,1,变量作用域,a,x,b,i,u,vc,d,c,i,38,静态链,为了在子过程的活动记录中引用包围它的某一外层过程的数据,在子过程的活动记录中添加静态链,静态链指向其直接外层过程的最新活动记录基地址。,sp,top,39,Program P; Var a,x: integer; Procedure Q(b:int ) Var i:integer; Procedure R( u:int; Var v:int); Var c,d: integer begin if
17、u=1 then R(u+1,v) V:=(a+c)*(b-d); end R begin R(1,x); endQ procedure S; var c,i: integer; begin a:=1; Q(c); end Sbegin a:=0; S; end,0,1,2,1,静态链示例1,P调用S时,P的活动记录,S的活动记录,SP,TOP,40,Program P; Var a,x: integer; Procedure Q(b:int ) Var i:integer; Procedure R( u:int; Var v:int); Var c,d: integer begin if u
18、=1 then R(u+1,v) V:=(a+c)*(b-d); end R begin R(1,x); endQ procedure S; var c,i: integer; begin a:=1; Q(c); end Sbegin a:=0; S; end,0,1,2,1,静态链示例2,过程S中调用Q时,P的活动记录,S的活动记录,Q的活动记录,SP,TOP,41,Program P; Var a,x: integer; Procedure Q(b:int ) Var i:integer; Procedure R( u:int; Var v:int); Var c,d: integer b
19、egin if u=1 then R(u+1,v) V:=(a+c)*(b-d); end R begin R(1,x); endQ procedure S; var c,i: integer; begin a:=1; Q(c); end Sbegin a:=0; S; end,0,1,2,1,静态链示例3,过程Q中调用R时,P,SP,TOP,S,Q,R,42,嵌套层次显示表(display)和活动记录,在本过程的活动记录中添加display表(栈式)用来存放各外层最新活动记录的基地址。,43,Display表示例,Program Main; 0 A:integer; procedure R;
20、1 B,C:integer; A:=B+C; end; procedure P;1 procedure Q;2 R; end; Q; end; P;End.,R的display,Q的display,过程层次,44,由于每个过程的display表的大小可在编译阶段知道,因此为便于组织存储区域和简化处理过程,可把display作为活动记录的一部分放在形式单元的上边。现在的活动记录为:,45,全局(老)display,全局display: 调用段display表地址,用来建立被调用段的display表:从调用段P1的display表中自底向上抄录L2个单元(L2为被调用段P2的层数)。,d,在编译时
21、可确定,46,Display的构建,当P1调用P2,进入P2后,P2如何建立自己的 display表?从P1的display表中自底向上抄录L2个单元(L2为P2的层数),再添加p2自己的sp,就构成p2的 display表。,47,一些嵌套调用示例,P1的display有 项:P2的display表有 项:,P1的display有 项:P2的display表有 项:,P0 -0层 P2 -1层 P1 -1层 call P2,(p0,p1),2,(p0,p1,p2),3,P0 -0层 P1 -1层 P2 -2层 call P2,2,2,(p0,p1),(p0,p2),48,一些嵌套调用示例(续
22、),P0 -0层 P2 -1层 P3 -1层 P1 -2层 call P2,P1的display有 项:P2的display表有 项:,3,2,(p0,p3,p1),(p0,p2),49,调子程序对应的目标动作,(par,_,_,Ti)的目标动作:(i+4)top:=Ti(Call,_,_P)的目标动作:1top:=sp; /保存老sp3top:=sp+d; /现行display地址4top:=n; /保存参数个数,50,调子程序对应的目标动作(续),进入过程P后的目标动作(确定新sp、top位置) sp:=top+1; top:=top+L; /L:活动记录长度 top:=top+V; /V
23、:数组体积 建立新的display R:=2sp; /取出老display的地址 dsp:=0R; d+1sp:=1R; (d+i-1)sp:=(i-1)R; (d+i)sp:=sp; /新加入的display项,抄老display,51,1sp:=返回地址;/保存返回地址,调子程序对应的目标动作(续),从过程返回: top:=sp-1; sp:=0sp; /取老sp 无条件转2top,52,Program P; Var a,x: integer; Procedure Q(b:int ) Var i:integer; Procedure R( u:int; Var v:int); Var c,
24、d: integer begin if u=1 then R(u+1,v) V:=(a+c)*(b-d); end R begin R(1,x); endQ procedure S; var c,i: integer; begin a:=1; Q(c); end Sbegin a:=0; S; end,0,1,2,1,Display表示例1,P调用S时,P的活动记录,S的活动记录,SP,TOP,display,0层(主程序)过程的display只含1项,这一项就是主程序开始工作时所建立的第1个SP值。,53,Program P; Var a,x: integer; Procedure Q(b:
25、int ) Var i:integer; Procedure R( u:int; Var v:int); Var c,d: integer begin if u=1 then R(u+1,v) V:=(a+c)*(b-d); end R begin R(1,x); endQ procedure S; var c,i: integer; begin a:=1; Q(c); end Sbegin a:=0; S; end,0,1,2,1,Display表示例2,过程S中调用Q时,P的活动记录,S的活动记录,SP,TOP,display,display,Q的活动记录,54,Program P; Va
26、r a,x: integer; Procedure Q(b:int ) Var i:integer; Procedure R( u:int; Var v:int); Var c,d: integer begin if u=1 then R(u+1,v) V:=(a+c)*(b-d); end R begin R(1,x); endQ procedure S; var c,i: integer; begin a:=1; Q(c); end Sbegin a:=0; S; end,0,1,2,1,Display表示例3,过程Q中调用R时,P,S,SP,TOP,Q,R,55,本章作业,设有如下所示代码段,试写出在过程S中调用过程Q后的栈式存储空间分配情况。(右边所示为动态存储分配采用的活动记录结构,假设存储空间分配从地址0开始),56,本章作业,Program P; Var a,b: integer; Procedure Q(x: int ) Var y: integer; begin end procedure S; var c,y: integer; begin c:=1; call Q(c); end begin call S( ); end,活动记录结构,57,