1、2018/10/19,1,Chapter9 Run-Time Environments,概 述 存储分配策略(Storage-allocation strategies) 访问非局部名字(Access to nonlocal names) 参数传递(Parameter passing),2018/10/19,2,9.1 概述,在生成目标代码之前,必须了解目标代码执行时的环境,1、空间环境,目标代码的运行都是在操作系统分配的一块存储区内进行的,这块存储区必须容纳目标代码和目标代码运行时的数据空间(目标代码中指令能访问的空间),2018/10/19,3,9.1 概述,编译程序分配目标程序运行时的数
2、据空间的基本依据是设计程序语言时对程序运行中存储空间的使用和管理办法的规定,代码生成器在生成目标代码时必须要体现该程序语言在设计时分配数据空间的规定,2018/10/19,4,9.1 概述,2、寄存器环境,寄存器是目标机器中的宝贵资源,如何分配寄存器提高程序运行的效率是代码生成器主要要解决的问题,(详细内容见下一讲),* 数据空间分配和寄存器分配最后都体现在生成的目标代码中,2018/10/19,5,9.1 概述,几种典型程序设计语言的特点,不同的语言有不同的分配数据空间的规定,有不同的组织运行时刻存储空间的方法,2018/10/19,6,9.1 概述,几种典型程序设计语言的特点与差异:, ,
3、 , , ,2018/10/19,7,9.1 概述,名字的联编(Bindings of Names),名字的左值 (l-value) 内存地址,存储名字的瞬时值,名字的右值 (r-value) 名字的瞬时值,名字的联编 将一个内存地址与一个名字联系起来,在程序的一次运行中,一个名字右值(瞬时值)可能会经常改变,一个名字也可能被联编到多个地址(如递归调用中),2018/10/19,8,9.1 概述,运行时刻内存的典型划分,操作系统收到运行目标程序的指令,分配一块连续的内存空间使目标程序在其上运行,P397 Fig7.7,这只是一个典型的划分,具体语言不同,该划分也不同,栈(Stack):支持过程
4、的递归调用,堆(Heap):支持动态内存申请,2018/10/19,9,9.1 概述,活动记录(Activation Records),是一段连续的存储区,用以存放过程的一次执行所需要的信息,如局部数据,活动记录的结构 P398 Fig7.8,参数域、状态域、数据域,TOP 指向栈顶,TOP-SP 指向局部数据区的开始位置,* 这只是一个一般的结构,具体语言不同,活动记录的结构和内容也有差异,2018/10/19,10,9.1 概述,过程的活动(Activation),过程的一次完整执行(第一条语句到最后一条语句),称为过程的一次活动,过程在执行中称它为活着的,如果允许递归调用,程序的一次执行
5、可能有同一个过程的多个实例是活着的,* 本章主要讨论不同语言的目标程序在运行时的存储分配策略及名字与其左值和右值的关系,2018/10/19,11,9.2 存储分配策略,静态存储分配(Static allocation),编译时确定所需的全部数据空间的大小,编译时安排好每个名字的存储位置(相对地址),采用静态存储分配的典型语言是 FORTRAN,2018/10/19,12,9.2 存储分配策略,FORTRAN 语言,程序是段结构的,主程序段 + 若干子程序(函数)段,没有动态数据类型,每个名字所需空间是确定的,没有递归调用,无需栈区,没有动态数据结构(如链表),没有动态内存申请,无需堆区,20
6、18/10/19,13,9.2 存储分配策略,因此,编译时可确定数据区大小,为每个名字分配好地址,FORTRAN 程序运行时内存划分为目标代码区和静态数据区,没有栈区和堆区,程序例子:P 402 example 7.4 Fig.7.10 Fig.7.11 Fig.7.12,2018/10/19,14,9.2 存储分配策略,程序设计语言若允许递归调用、可变数组、可变数据结构:,编译时无法确定运行时需要的存储空间大小,动态存储分配包括: 栈式分配(Stack allocation)和 堆式分配(Heap allocation),只能在运行时动态地确定,采用动态存储分配,2018/10/19,15,
7、9.2 存储分配策略,栈式存储分配的思想:,在运行空间中划分一块存储空间作为栈区,程序运行时每当调用一个过程,就将该过程的活动记录压入栈中,过程执行完毕将它的活动记录从栈中弹出,例子:P405 Fig.7.13,2018/10/19,16,9.2 存储分配策略,栈式存储分配的实现,反映在目标代码生成器的构造策略中,最终体现在生成的目标代码中,调用序列(Call Sequence) 目标代码中的一个指令序列,完成调用一个过程的一系列操作,包括为被调用过程分配一个活动记录,并在相应的域中填入信息,2018/10/19,17,9.2 存储分配策略,返回序列(Return Sequence) 目标代码
8、中的一个指令序列,完成从一个被调用过程返回到它的调用过程的一系列操作,包括释放被调用过程的活动记录,并复制出返回值,具体内容:P407 Fig.7.14,2018/10/19,18,9.2 存储分配策略,有些程序设计语言允许用户自由申请内存空间,如 C 的 malloc 和 free,PASCAL 的 new 和 dispose,何时申请何时释放由用户决定,有些程序设计语言在进行数据空间分配时不服从栈式的存储分配原则(即“先申请后释放、后申请先释放”),2018/10/19,19,9.2 存储分配策略,这时采用堆式存储分配方案,运行空间划分一块堆区,用户(显式)或目标代码需要时从这堆中借用一块
9、,不用时退还,* 这是一种完全动态的存储分配方案,2018/10/19,20,9.3 访问非局部名字,非局部名字,相对于引用点所在的过程或分程序来说的,程序执行时引用的在当前过程(分程序)之外定义的变量称为非局部名字,* 本节主要讨论栈式存储分配中如何访问非局部名字,2018/10/19,21,9.3 访问非局部名字,最近嵌套的作用域规则(most closely nested rule),P 412 这是大多数程序设计语言采用的作用域规则,例子:P416 Fig.7.22(过程),例子:P413 Fig.7.18(块或分程序),2018/10/19,22,9.3 访问非局部名字,分程序(块)
10、结构 ALGOL 和 C,分程序(块)含有局部数据说明的语句序列,有定界符号,允许嵌套,例子:P413 Fig.7.18,2018/10/19,23,9.3 访问非局部名字,过程(活动记录)采用栈式存储分配,分程序中的局部数据在活动记录的局部数据区内也采用栈式分配,并列的分程序中的名字可分配同一个地址,因为它们所属的分程序不会在同一时刻存活,访问同一过程中的局部名字可以从栈顶到栈底进行定位(同一活动记录中)Fig.7.19,* 访问其它过程中的局部名字见下面的讨论,2018/10/19,24,9.3 访问非局部名字,无过程嵌套定义 C,程序中引用的名字或者在当前的过程中被定义,或者在所有过程之
11、外被定义,在所有过程之外被定义的变量,称之为全局变量,被存放在静态数据区,地址在编译时刻确定(放符号表中),2018/10/19,25,9.3 访问非局部名字,过程中的局部名字联编到过程的活动记录的局部数据区中,访问非局部名字可根据符号表中该变量的地址直接到全局静态数据区中查找,2018/10/19,26,9.3 访问非局部名字,有过程嵌套定义 PASCAL,PASCAL语言的一个例子 P416,过程和函数的嵌套深度(nesting depth),访问非局部名字的方法有两种,一种是通过存取链,一种是通过 DISPLAY 表,主程序为 1,2018/10/19,27,9.3 访问非局部名字,存取
12、链(access links),活动记录中的一个区,是一个指针,若过程 P 直接嵌入在过程 Q 中,则 P 的活动记录中的存取链指向 Q 的最近的活动记录中的存取链,2018/10/19,28,9.3 访问非局部名字,如何建立存取链?,参见 P417 Fig. 7.23(a)(b)(c)(d),区分两种情况: P418,1、Np Nx , Np 是调用者深度, Nx是被调用者深度,2、Np = Nx NpNx+1,2018/10/19,29,9.3 访问非局部名字,如何利用存取链访问非局部名字?,根据当前过程的嵌套深度和非局部名字所在过程的嵌套深度,可以计算出需顺着存取链前进的步数,从而对非局
13、部名字进行访问 NpNa,2018/10/19,30,9.3 访问非局部名字,DISPLAY表,是一个指向活动记录的指针数组,运行时刻要访问的嵌套深度为 i 的非局部名字就在 d i 所指的活动记录中,这种方法只要一步就可到达要访问的非局部名字的活动记录,比存取链方法高效,2018/10/19,31,9.3 访问非局部名字,如何建立与维护 DISPLAY 表?,DISPLAY 表的大小由程序中嵌套深度最大的过程决定,参见 P421 Fig.7.26 (a)(b)(c)(d),2018/10/19,32,9.3 访问非局部名字,当建立嵌套深度为 i 的过程的活动记录时,首先在新的活动记录中保存
14、d i 的值,然后置 d i 指向新的活动记录,注意考虑调用和返回两种情况,当从过程中返回时,从活动记录中恢复老的 d i 的值,2018/10/19,33,9.4 参数传递,传值调用(Call-by-Value),把实在参数的右值传递给形式参数,调用序列计算实在参数的值,并将它复制到被调用过程活动记录的参数域中,这种参数传递方法对调用过程的活动记录没有影响,2018/10/19,34,9.4 参数传递,引用调用(Call-by-Reference),把实在参数的左值(地址)传递给形式参数,调用序列将实在参数的左值(地址)复制到被调用过程活动记录的参数域中,被调用过程对形式参数的引用其实是对实
15、在参数的地址(在被调用过程的活动记录中)进行引用,2018/10/19,35,9.4 参数传递,复制恢复(Copy-Restore),传递参数同传值调用,在从被调用过程返回前,把形式参数的现行右值复制回相应的实在参数的左值中,这种方法与引用调用是有区别的: 在被调用过程体中访问了非局部名字,而此非局部名字正是形式参数时 P427 Fig. 7.31,2018/10/19,36,9.4 参数传递,传名调用(Call-by-Name),用实在参数对过程体进行宏扩展,必要时对被调用过程中的名字进行改名(防止与调用过程中的名字重复),* 例子:P456 练习7.6 Fig.7.53,2018/10/19,37,The End!,