第七章运行环境.ppt
第七章运行环境,源语言问题存储组织存储分配策略访问非局部名字参数传递,静态和动态,静态和动态的联系名字和数据对象数据对象的动态表示名字的作用域数据对象的存储分配过程和活动参数处理运行时支撑程序包,源语言问题(1),过程及其执行过程定义:是一个声明,最简单形式是把一个标识符和一个语句联系起来该标识符称为过程名语句是过程体函数:返回值的过程过程调用过程名出现在可执行语句中时,则称这个过程在这点被调用调用者、被调用者和调用点参数形式参数:与局部变量有不同实在参数:在调用点,将其传递给被调用的过程,源语言问题(2),program sort(input,output);var a:array 0.10 of integer;procedure readarray;var i:integer;begin for i:=1 to 9 read(ai)end;function partition(m,n:integer);var i,j,x,v:integer;begin.end;procedure quicksort(m,n:integer);var i:integer;begin if(n m)then begin i:=partiton(m,n);quicksort(m,i 1);quicksort(i+1,n);end end;begin a 0:=-9999;a 10:=9999;readarray;quicksort(1,9)end.,源语言问题(3),过程的执行的表示:活动树控制流的假设控制流是连续的过程的每次执行都从过程体的起点开始,最后控制返回到直接跟随本次调用点的位置过程间的控制流可以用树表示(调用树)活动:过程体的一次执行活动的生存期:过程体执行的第一步和最后一步之间的步序列包括直接或间接被这个过程调用的其它过程的时间过程的活动的生存期要么是不重叠的,要么是嵌套的递归:如果一个过程的前一个活动结束前,它的一个新的活动又开始递归的两种缘起,源语言问题(4),活动树描述控制进入和离开活动:每个结点代表过程的一个活动根结点代表主程序的活动结点a是结点b的父结点,当且仅当控制流从a的活动进入ba结点处于b结点的左边,当且仅当a的生存期先于b的生存期结点和活动是一一对应的,源语言问题(5),幻灯片4中程序的活动树,源语言问题(6),控制栈控制栈用于保存活跃着的过程栈的内容表示活动树上到根结点的一条路径,源语言问题(7),名字与数据对象声明的作用域区分同名程序声明:最接近的嵌套规则int a,b;int*p;int foo(int a)int b,c;char*p;p=malloc(sizeof(char);b=foo(1);作用域:一个声明起作用的程序部分称为该声明的作用域局部和非局部:过程中名字的出现,如果是在该过程的一个声明的作用域内,则这个出现称为局部于该过程,源语言问题(8),名字的结合程序中声明的名字和动态数据对象的关系数据对象是保存值的存储单元一个名字可能代表不同的数据对象环境:表示将名字映射到存储单元(即名字的左值)的函数状态:表示将存储单元映射到它所保存的值(即名字的右值)的函数赋值操作只改变状态,但不改变环境结合:如果环境把存储单元s联系到名字x,则称x结合到s,这个联系本身称为x的结合静态概念动态概念 过程的定义过程的活动 名字的声明名字的结合 声明的作用域结合的生存期,源语言问题(9),名字结合要考虑的问题过程是否递归当控制从过程的活动返回时,局部名字的值是否要保留过程能否引用非局部的名字过程调用时参数是如何传递的过程是否可以作为参数被传递过程能否作为结果值返回存储区能否在程序控制下动态地分配存储区是否必须显式地释放,存储组织(1),运行时内存的划分程序运行时如何使用内存:目标代码的存放目标代码可以存放在静态确定的区域,其长度在编译时即可确定数据对象可静态分配的数据对象,其地址可以编译到目标代码中动态分配控制栈:记录过程活动堆:可以存放动态分配的数据等,但开销要比栈大,存储组织(2),活动记录定义:过程的一次执行所需要的信息用一块连续的存储区来管理,这块存储区叫做活动记录或帧一般的活动记录结构如右图,但不是所有的语言或编译器都是如此寄存器的使用活动记录的操作:过程被调用时入栈过程返回时出栈,存储组织(3),活动记录的各个域的作用临时数据域:临时变量的存储等局部数据域:局部于过程执行的数据机器状态:保存过程调用前的机器状态信息返回地址可选的访问链:用于非局部数据的访问可选的控制链:指向调用者的活动记录实在参数域:参数个数较少时,可以考虑用寄存器传递,效率高;参数多时用用这个域传递返回值域:用于存放被调用过程返回给调用过程的值,也可以用寄存器返回(效率高,但形式受限制),存储组织(4),过程调用时,每个域的长度都可以确定,大部分域的长度可以在编译时刻确定运行框架安排的设计这个框架的设计要考虑指令集体系结构(ISA)特性和要编译的程序语言本身的特点有些系统提供商为他们的体系结构提供一种标准的运行框架安排,使得可以实现不同的语言编写的函数间的调用这个运行框架的设计通常在运行时支撑系统要考虑,存储组织(5),编译时的局部数据安排名字所需的存储空间的大小可以由它的类型确定基本数据对象(字符、整数或实数)可以用几个连续的字节保存聚合类型(记录、数组等)一块足够大的空间存放所有成分连续的字节区局部数据域在编译过程中的声明时安排长度可变的数据不保存在这个区域数据对象的相对地址(偏移)是数据对象地址相对于特定点(例如活动记录的开始点)的地址差数据对齐:数据对象的存储安排受目标机器寻址限制的影响如10个字符的数组可以被编译器分配12个字节不用的2个字节称为衬垫空白区,存储组织(6),两个C编译器的数据布局形式:,存储分配策略(1),静态分配名字的结合:编译时实现,不需要运行时支撑程序名字和存储的结合是固定的允许名字的值在过程停止活动后保持根据名字的类型,编译器可以在静态时刻确定该名字所需的存储空间,效率局限:数据对象的长度和它在内存中位置的限制必须在编译时知道不允许递归过程不允许有动态数据结构在FORTRAN77中,静态存储分配策略,每个过程的活动记录可以与它的代码放在一起,存储分配策略(2),PROGRAM CNSUMECHARACTER*50BUFINTEGERNEXTCHARACTERC,PRDUCEDATA NEXT/1/,BUF/6C=PRDUCE()BUF(NEXT,NEXT)=CNEXT=NEXT+1IF(C.NE.)GOTO 6WRITE(*,(A)BUFENDCHARACTER FUNCTION PRDUCE()CHARACTER*80BUFFERINTEGERNEXTSAVE BUFFER,NEXTDATA NEXT/81/IF(NEXT.GT.80)THENREAD(*,(A)BUFFERNEXT=1END IFPRDUCE=BUFFER(NEXT,NEXT)NEXT=NEXT+1END,存储分配策略(3),存储分配策略(4),栈分配递归过程要求栈分配允许动态数据对象的存在满足先入后出的次序控制栈:活动记录和栈的关系局部量的存储空间包含在对应调用的活动记录中局部量的值会随着过程调用结束而丢失控制栈top的变化和活动记录的长度相关通常,局部量相对于活动记录开始点的偏移量可以记录在符号表中,存储分配策略(5),例:活动记录在栈中的分配,存储分配策略(6),例:编译时确定长度的数据对象偏移量在符号表中的存放,存储分配策略(7),过程调用的实现:通过过程调用序列(calling sequences)实现,包括调用序列和返回序列调用序列:分配活动记录,信息的保存和填充参数、返回地址、老的控制栈top、寄存器的保存、局部对象的初始化等返回序列:恢复机器状态,是调用者继续执行返回值、寄存器的恢复、重置控制栈top、跳转到返回地址调用序列和活动记录不是一成不变的,语言、实现等都会导致不同过程调用序列的代码分成两部分,分别由调用过程和被调用过程负责,但划分的界限不是唯一的,存储分配策略(8),图:调用者和被调用者间的任务划分,存储分配策略(9),调用序列(call sequences),过程p调用过程qp计算实参,依次放入q的活动记录中(也可以利用寄存器传递参数),改变top_sp的值p把返回地址和当前的base_sp的值存入q的活动记录中,建立q的访问链,增加base_sp的值q保存寄存器的值(包括top_sp的值)和其它机器状态信息q增加top_sp的值,初始化它的局部数据,并开始执行,存储分配策略(10),返回序列(return sequences),过程p调用过程qq把返回值置入邻近p的活动记录的地方(也可以利用特定的寄存器传递返回值)q恢复寄存器的值(包括top_sp和base_sp)和其它机器状态信息q根据返回地址将控制返回pp根据参数个数调整top_sp的值,把返回值放入自己的活动记录中参数个数不确定?C语言中的printf保存描述参数的信息,如printf首先处理的第一个变元(它的位置是确定的)指出了其它参数的性质,存储分配策略(11),动态数组数组的界在编译时刻无法确定,只能在运行时刻确定procedure(n)integer n,mnend动态数组不能分配在活动记录中在动态数组的大小确定后,在该过程的活动记录紧邻的栈中分配空间动态数组的描述也可以考虑用内情向量的方式,存储分配策略(12),动态数组访问的示意,存储分配策略(13),悬空引用引用某个已释放的存储单元称为悬空引用这是由于存储空间可以释放这是一个逻辑错误:在大多数语言中,释放的存储单元的值是没有定义的main()int*p;p:=dangle();int*dangle()int i=23;return 函数dangle返回后,指针p指向一个已释放的存储单元,存储分配策略(14),堆分配栈分配的局限不能使得活动停止时,局部名字的值保持被调用者的活动不能比调用者的活动活得更长(否则,活动树不能正确描绘过程间的控制流),只能满足先入后出的次序堆分配把连续存储区域分成块需要时分配(活动记录或其它对象)释放次序任意堆可以与栈分配结合起来C+语言中,基本保持栈的形式,堆用于动态对象的分配C、Pascal语言中的指针的动态分配等,存储分配策略(15),堆中,活跃的活动记录不一定相临,可能存在空洞,存储分配策略(16),堆的释放不释放存储空间溢出时停止显式释放Free(C,PL/1),deallocation(Ada)有可能引起悬挂引用隐式释放单引用引用计数垃圾收集(Garbage collection)堆的分配和释放的优化,访问非局部名字(1),如何通过活动记录正确访问名字,满足作用域的要求?两种作用域静态作用域根据程序正文决定用于名字的声明最接近的嵌套规则程序块非局部名字的访问:访问链动态作用域在运行时,根据现行的活动来决定用于名字的声明,如Lisp等,访问非局部名字(2),程序块定义:起源于AlgolC语言中的定义:declarations statements 允许嵌套最接近的嵌套规则:程序块B中声明的作用域包括B如果名字x没有在B中声明,则B中x的出现是在外围程序块B的x声明的作用域中,且满足:B有x的声明B比其它任何含x声明的程序块更接近被嵌套的B,访问非局部名字(3),例:非局部名字的引用,访问非局部名字(4),名字的存储分配:基于栈分配:声明的作用域决不超出它所在的程序块程序块当作无参的过程,所以活动记录可以是程序块的这类“过程”调用不需要参数传递,控制只能沿静态正文进入和退出程序块每次为一个完整的过程体分配存储空间只为过程分配活动记录过程内嵌套的程序块的生命所需要的存储空间由过程负责留出要分配的变量在编译时刻确定,不考虑控制流作用域不重叠的声明可以共享一个存储区域a0b0b1 a2,b3,访问非局部名字(5),无过程嵌套的静态作用域一个过程的定义不能出现在另一个过程里面,如C语言对某个过程非局部的名字(过程中程序块的非局部名字的处理见前面的内容),其声明在所有函数之外函数外声明的作用域:该声明后的所有函数体,但同名的局部声明的出现将掩盖全局声明的作用例:int a 11;readarray()a int partition(y,z)int y,z;a quicksort(m,n)int m,n;main()a,访问非局部名字(6),存储分配比较简单:声明在任何过程外的所有名字的存储单元静态分配,其存储位置在编译时可知过程中可见的名字:非过程局部的,则使用静态确定的地址访问其它的名字,局部于栈顶的活动纪录,可以通过base_sp访问重要好处:程序中声明的过程可以作为参数传递和作为结果返回(C语言中是传递过程的指针)非局部名字的访问不受过程激活方式的影响,访问非局部名字(7),例:program pass(input,output);var m:integer;function f(n:integer):integer;begin f:=m+n end f;function g(n:integer):integer;begin g:=m*n end g;procedure b(function h(n:integer):integer);begin write(h(2)end b;beginm:=0;b(f);b(g);writelnend.变量m对所有的过程是非局部的,可以静态分配它的存储单元执行结果是:2 0,访问非局部名字(8),有过程嵌套的静态作用域非局部名字的访问:寻找最接近的嵌套声明,如下面例子中函数partition对a的访问 program sort(input,output);var a:array 0.10 of integer;x:integer;procedure readarray;var i:integer;begin a end readarray;procedure exchange(i,j:integer);begin x:=ai;ai:=aj;aj:=x;end exchange;procedure quicksort(m,n:integer);var k,v:integer;function partition(y,z:integer):integervar i,j:integer;begin a;v;exchange(i,j);end begin end quicksort;begin end sort.,访问非局部名字(9),嵌套深度:过程的嵌套深度设主程序的嵌套深度是1从一个过程进入一个被包围的过程,嵌套深度加1名字的每次出现,把它的声明所在过程的嵌套深度作为该名字的嵌套深度用于实现静态作用域访问链:为每个活动记录增加一个访问链指针,如果过程p在源程序中直接嵌在q中,则p的活动记录的访问链指向最接近的那个q的活动记录的访问链实现过程嵌套的静态作用域,访问非局部名字(10),幻灯片41程序执行时运行栈的瞬像:,访问非局部名字(11),静态链(访问链)和动态链(控制链)的比较:静态链:指向源程序中包围本过程的直接外层过程的一个最近活动记录的访问链通过静态链实现非局部名字的访问通过静态链访问非局部名字,访问速度慢动态链:指向调用者的活动记录,A,B,C,D,E,AR of A,AR of B,AR of C,AR of D,动态链,静态链,访问非局部名字(12),访问链的建立:假定过程p的嵌套深度为np,它调用一个嵌套深度为nx的过程x:如果np=nx:如幻灯片44的C调用E过程x和p的嵌套深度为1,2,nx 1的外围过程必定相同从调用过程追踪访问链 np-nx+1次,即到达静态包围x和p的最内过程的最新活动记录,这个访问链就是x必须指向的访问链np-nx+1可以在编译时计算特别地,如果np=nx,如幻灯片44的C调用D,则D的访问链指向与C的访问链指向相同,访问非局部名字(13),通过访问链访问非局部名字:假定过程p的嵌套深度为np,它引用一个嵌套深度为na的变量a,na=np,则a的存储单元可以如下找到:当控制在p中时,p的一个活动记录必定在栈顶从栈顶追踪访问链np-na次(这个次数编译时刻可以计算)追踪访问链np-na次后,到达a的声明所在过程的活动记录,根据a的偏移量可以访问综述:过程p中变量a的地址可以如下表示:(np-na,a在活动记录中的偏移)过程调用发生时,非局部名字到存储单元的结合可能发生变化,访问非局部名字(14),过程作参数的处理:作为参数传递的过程必须取它的访问链和它一起传递例:program param(input,output);procedure b(function h(n:integer):integer);begin writeln(h(2)end b procedure c;var m:integer;function f(n:integer):integer;begin f:=m+n end f;begin m:=0;b(f)end c;begin cend.,访问非局部名字(15),访问非局部名字(16),Display表:di通过数组存放访问链,提高对非局部名字的访问效率如果当前的活动记录对应的过程p的嵌套深度为j,则Display表有j项,前j-1项指向依次嵌套在过程p外面的过程的最新活动记录,第j项指向过程p的这次活动记录同一个过程的不同活动记录间可以利用访问链链结,在调用序列和返回序列中进行更新操作当一个嵌套深度i的过程被调用时,建立一个新的活动记录在新的活动记录中保存d i 的值令d i 指向新的活动记录在一个活动结束前,恢复d i 被保存的值,访问非局部名字(17),访问非局部名字(18),Display表的维护如果 j=i,调用者和被调用者外面包围的嵌套深度为1,2,i-1的过程是同样的,在新的活动记录中保存老的d i,置d i 指向新的活动记录,保持Display表的前i 1项不变,如上页的d图Display表的保存可以放在寄存器中,受限于寄存器个数和非局部名字访问的频繁程度可以在编译时刻知道Display表的最大长度,因此也可以将Display表存放为一个单独的表或作为活动记录的一部分,访问非局部名字(19),动态作用域:当一个过程被调用时,它的非局部名字到存储单元的结合不会改变,即被调用过程的非局部名字a和它在调用过程中引用的是同样的存储单元新的结合仅为被调用过程的局部名字建立,这些名字占用新活动记录中的存储单元在运行时,一个名字a实施其影响,直到含a的声明的一个过程开始执行时暂停;此过程停止时,该影响恢复,访问非局部名字(20),例:动态作用域和静态作用域的对比program dynamic(input,output);var r:real;procedure show;begin write(r:5:3)end;procedure small;var r:real;begin r:=0.125;show end;beginr:=0.25;show;small;writeln;show;small;writeln;end静态作用域的输出:动态作用域的输出:0.250 0.250 0.250 0.1250.250 0.250 0.250 0.125,访问非局部名字(21),动态作用域的实现:深访问访问链指向的活动记录和控制链指向的活动记录一样,则实现了动态作用域,因此可以省却访问链,通过控制链搜索栈编译时刻不能确定搜索的深度访问非局部名字需要较长的时间,但活动的开始和结束不需要额外开销浅访问为每个名字在静态分配的存储空间中保存它的当前值当过程p的新活动出现时,p的局部名字n接管分配给n的存储单元,n的先前值可以保存在p的活动记录中,当p的活动结束时再恢复可以直接访问非局部名字,但活动的开始和结束需要维护的开销,参数传递(1),过程和其调用者交换信息的方法:非局部名字参数返回值参数传递的方法:实现形参和实参联系的方法不同的参数传递方法是因为对表达式所表达的内容的不同解释左值右值参数传递的种类值调用(Call-by-Value)引用调用(Call-by-Reference)复写-恢复调用(Copy-Restore)换名调用(Call-by-Name),参数传递(2),值调用计算实参,并把它的右值传递给被调用过程C语言、Pascal语言支持这种方式形参当作局部名,其存储单元在被调用过程的活动记录中调用者计算实参,并把右值放在形参的存储单元中形参的运算不影响调用者活动记录中的值被调用过程可以通过非局部名字或通过显式传递的指针值影响调用者,参数传递(3),swap(x,y)int*x,*y;int temp;temp=*x;*x=*y;*y=temp;main()int a=1,b=2;swap(,参数传递(4),引用调用也称为地址调用(call-by-address)或位置调用(call-by-location)调用者将参数的左值传递给被调用的过程如果实参是有左值的名字或表达式,则传递这个左值本身如果实参实没有左值的表达式(如a+b或2等),则把它的值计算到新的存储单元,然后传递这个单元的地址如Pascal语言中var参数是用这种方式传递的数组一般是用这种方式传递的,参数传递(5),复写-恢复值调用和引用调用的混合,也称为复写入和复写出(copy-in copy-out)或值-结果(value-result)在控制流到被调用过程之前计算实参,实参的右值按值调用方式传递被调用过程,如果实参有左值,则在调用前确定它的左值当控制返回时,将形参的当前右值复写回到实参的左值,该左值是上述调用前确定的左值。只有有左值的实参被复写某些Fortran的实现使用了这种参数传递方式,参数传递(6),使用复写-恢复方式的原因避免了一个过程调用有多于一种方法访问一个变量的问题program copyout(input,output);var a:integer;procedure unsafe(x:integer);begin x:=2;a:=0 end;begin a:=1;unsafe(a);writeln(a)end如果采用值调用,输出为0;如果采用引用调用,输出为0,如果采用复写-恢复方式,输出为2避免了存储对齐可能引起的问题。在结构中的小的数据对象被打包后,不需要对齐,参数传递(7),换名调用:历史上,由Algol的复写规则定义把过程看成是宏,即用它的体替换调用者的调用,用实参的文字代替形参,这样的文字代替称为宏展开或内联展开被调用过程的局部名和调用过程的名字保持区别,可以认为在宏展开前,被调用过程的每个局部名系统地被重新命名成可区别的名字为保持实参的完整性,实参可以在括号包围例如调用swap(i,a i)temp:=i;i:=a i;a i:=temp;如果采用换名调用,结果不正确,参数传递(8),换名调用主要出于理论上的兴趣,实践中较少采用In-lining是一种比较常用的类似技术,如果结合执行的profling信息,In-lining可能是一种比较有效的优化技术,习题,陈意云书的6.1和6.2,