1、1,第五章 运行环境,源程序最终需要运行,因此需要了解:与源程序等价的目标程序如何在内存中运行,为了程序的正确运行需要什么样的支持。 不同的源语言结构,所需的运行环境和支持不同。本章仅以最简单的、基于过程的、顺序执行的程序为前提讨论,即源程序的基本结构是顺序执行的过程,过程与过程之间仅通过子程序调用的方式进行控制流的转移。 讨论内容: 静态的过程运行时具有什么样的动态特性; 运行时需要什么样的环境支持(存储空间分配); 过程之间的调用与返回应如何实现等。,2,5.1 过程的动态特性,过程与活动 过程、活动、生存期 过程的每一次运行称为一次活动(activation)。活动是一个动态的概念,它有
2、有限的生存期(life time)。 定义5.1 活动的生存期是指从进入活动的第一条指令执行到离开此活动前的最后一条指令执行的这段时间,其中包括调用其它过程时其它活动的生存期。活动之间的通信 子程序调用:call/return,有去必有回 顺序调用:生存期不交 嵌套调用:生存期嵌套 消息传递:send/receive,可有去无回,3,5.1 过程的动态特性,顺序执行程序的控制流 特点:程序的执行在时间上是顺序的和排他的。即在程序执行的任一瞬间,有且仅有一个活动正在活动。 控制流满足: 控制流是连续的; 过程间的控制流可以用树来表示。活动树 定义5.2 描绘控制进入和离开活动方式的树结构被称为活
3、动树 每个结点代表过程的一个活动; 根代表主程序的活动; 结点a是b的父亲,当且仅当控制流从a的活动进入b的活动 结点a处于b的左边,当且仅当a的生存期先于b的生存期。,4,5.1 过程的动态特性,例5.1 P244 例5.2,活动树的实质是反映了顺序执行程序的调用和时序关系,它把每个活动的生存期缩小到了一点。,5,5.1 过程的动态特性,控制栈与活动记录 控制流与活动树的关系 程序控制流:一个完整程序的控制流恰好是对它活动树的一次深度优先遍历。 活动树上各节点之间具有下述关系: 同一层次的活动生存期不交 任一时刻,处在生存期的活动构成一条从根到某节点的路径 路径上各节点生存期是嵌套的 控制栈
4、与活动记录 控制栈:保存所有在生存期的活动(一条后进先出的路径)。 活动记录: activation record,栈中的每个元素称为一个活动记录,为活动提供的活动场所。,6,5.1 过程的动态特性,活动记录中至少应该存放两类信息: 控制信息:控制活动的正确调用与返回和控制活动记录的正确切换; 访问信息:用于为当前活动提供对数据,如本地数据和非本地数据的访问。 例5.2 快排序程序运行时某状态的控制栈 控制栈保存了任何时刻所有在生存期活动的活动记录,控制栈的一个状态,生存期,正在运行,7,5.1 过程的动态特性,名字的绑定 名字的绑定 定义5.3 运行时为名字X分配存储空间S,这一过程称为绑定
5、(binding)。 换句话说,绑定是名字X与存储空间S的结合。 此处,X是一个对象: 既可以是数据对象,如变量,与之结合的是一个存储单元; 也可以是操作对象,如过程,与之结合的是可执行的代码; 我们的讨论仅限于X是一个数据对象。静态与动态 名字的声明与名字的绑定均需要有对应的存储空间,而存储空间的对应方式,一个是静态的,一个是动态的。,8,5.1 过程的动态特性,声明时关心的是声明的作用域,即当一个名字被引用时,在不同的作用域中与该名字的不同声明结合;名字和作用域是静态的一对一关系。 绑定时关心的是绑定的生存期,即当一个名字在运行时被实际分配的存储单元,名字与存储单元结合的这段时间被称为绑定
6、的生存期,显然此生存期应该和名字的生存期一致。 同时,运行时名字与存储单元的结合可以是一对多的关系。,5.1 过程的动态特性,变量与值的两步映射,在名字绑定的概念下,对变量的赋值通过两步映射来实现。 经过名字的绑定将名字映射到一个实际的存储空间 经过赋值将此空间映射到一个实际的值,10,5.1 过程的动态特性,变量与值的两步映射,在名字绑定的概念下,对常量的赋值实质上就是直接将名字 与一个具体的值绑定,11,5.1 过程的动态特性,变量与值的两步映射,例5.3 若有变量声明x: real和常量声明const pi=3.14,则赋值句中变量和常量的映射关系:,12,5.1 过程的动态特性,环境与
7、状态 允许递归调用的情况下,同一作用域中的一个名字,可以同时绑定到多个存储单元,环境是一对多映射。 一个存储单元可以存放不同的值,状态也是一对多映射。影响存储分配策略的因素 编译器怎样对存储空间进行组织,采用什么样的存储分配策略,很大程度上取决于程序设计语言采用的机制,如:过程能否递归过程能否嵌套过程调用时参数如何传递哪些实体可以作为参数和返回值是否允许动态的为对象分配和撤销存储空间存储空间是否必须显式地释放等等,13,5.2 运行时数据空间的组织,运行时内存的划分与数据空间的存储分配策略,可执行代码的大小在编译时可以静态确定,因此它们放在静态数据区。 数据可以有三中存储方式,静态数据区,栈,
8、堆 静态区存放一对一的绑定且编译时就可以确定存储空间大小的数据 栈用于存放一对多的绑定去与活动同生存期的数据 堆用于存放于活动生存期不一致且可以动态生成和撤销的数据,14,5.2 运行时数据空间的组织,运行时内存的划分与数据空间的存储分配策略,数据区与分配策略: 静态分配策略:编译时安排所有数据对象的存储,即绑定是静态确定的; 栈分配策略:按栈方式自动管理运行时的数据存储空间 堆分配策略:在运行时根据要求从堆数据区动态地分配和释放数据存储空间。,15,5.2 运行时数据空间的组织,静态与动态分配简介 静态分配策略 特点:绑定是1对1的映射。名字在程序编译时与存储空间结合,每次过程活动时,它的名
9、字映射到同一存储单元。程序运行时不再有对存储空间的分配。,限制: 数据对象的大小和它在内存中位置的限制必须在编译时确定,如数组的大小不能是动态的; 不允许程序递归,因为一个过程的所有活动使用同样的名字绑定,即绑定是一对一的; 不能动态生成或撤消数据,因为运行时没有存储分配机制,16,5.2 运行时数据空间的组织,早期的FORTRAN完全采用静态分配。一般在整个程序运行的期间被共享的内容都可以才去静态分配。,栈分配策略 特点:按栈的方式自动分配存储空间 限制: 活动停止后,局部于活动的名字值不能保持(否则悬空引用) 无法处理需要随时分配或撤销(不满足LIFO)的动态数据 被调用者的活动比调用者的
10、活得更长,17,5.2 运行时数据空间的组织,堆分配策略 特点:可任意分配和撤消数据;对程序设计语言没有限制; 静态、栈与堆的关系: 可以静态分配的数据均可以栈分配 可以静态和栈分配的数据均可以堆分配 反之不一定 堆分配的基本思想:,18,5.3 栈式动态分配,控制栈中的活动记录 活动记录的具体内容(控制信息访问信息),参数与返回值:存放实参和返回值 控制链(可选):指向调用者活动记录的指针,用于当调用返回时,将当前栈顶正确切换到调用者的活动记录; 访问链(可选):用于在可嵌套定义的过程中指示访问非本地数据; 调用时需要保存的机器状态:如程序计数器,寄存器等;,过程内部声明的数据:如 VAR
11、X,Y :INTEGER等; 临时变量:源程序中不出现的、由编译程序产生的变量,如表达式x+y+z求值时产生的T1,T2,.;,5.3 栈式动态分配,控制栈的两个重要指针(全程量) top:控制栈的栈顶 sp:数据访问。活动记录中的所有数据均可以是相对于sp的偏移量。 在整个的程序运行过程中,top指示栈顶,sp指示当前栈顶的活动记录。,20,5.3 栈式动态分配,例5.5 控制栈的变化过程srq(1,9)q(1,3)q(1,0),通过例子来看程序运行时控 制栈中的活动记录怎么变化,21,5.3 栈式动态分配,调用序列与返回序列,问题:如何使得过程调用和返回时能够实现: 程序控制流正确转移 活
12、动记录正确切换 解决方案:在过程运行代码的适当位置,加入实现这些功能的代码,称它们为调用序列和返回序列。,22,5.3 栈式动态分配,调用序列和返回序列的位置 调用序列:、 返回序列:、,23,5.3 栈式动态分配,调用序列和返回序列的内容,调用者 传递参数 维护访问链(如果必要) 保存返回地址和控制链 保存机器状态(指令计数器寄存器等) 将控制转向被调用者,被调用者 设置新的活动记录大小 为可变数组分配空间(若有) 初始化本地数据(若必要) .(开始可执行代码),被调用者 保留返回值(若为函数) 恢复访问链(如果必要) 恢复调用时机器状态 恢复控制链 将控制返回调用者,调用者 接收返回值(若
13、有的话) .(继续执行代码),分工原则:尽量将调用序列和返回序列放在被调用者中,24,25,5.3 栈式动态分配,栈式分配中对非本地名字的访问 允许嵌套定义过程的语言 名字在不同的作用域内,可以有不同的定义(静态作用域最近嵌套)。 如何通过当前的活动记录访问非本地数据: 前提:每个名字均可以通过其所在活动记录的sp寻址 设法:任何活动记录内,均可得到其外层活动记录的sp访问链 通过引入访问链实现对非本地名字的访问,其具有双重作用: 本身所在的位置(左值),可以作为本活动记录中数据的相对地址的基址sp; 内容(右值)指向它直接外层过程的、最新出现的活动记录的访问链。,26,5.3 栈式动态分配,
14、利用访问链访问非本地数据 设过程p的嵌套深度是np,过程p中引用一个嵌套深度为na且nanp的变量a。设p和a的层次之差为x,即x= np-na。则a的存储可以如下方式找到: 当控制在p中,p的一个活动记录肯定在栈顶。从栈顶的活动记录中追踪访问链np-na次。(np-na的值是静态作用域规则决定的,可以在编译时计算得到); 追踪访问链np-na次后,找到a的声明所在过程的活动记录的访问链sp(a)。,27,5.3 栈式动态分配,与本地变量相比,非本地变量的地址需要两个信息(np-na, a), 可存放在符号表a的条目中,用于生成存取变量a的代码。 注意: 本地变量仅需偏移量,非本地变量还需要层
15、次差; 层次差与偏移量一样,均可以静态确定 例:在p(1,3)中访问s中变量a,因为np-na=2,所以追踪访问链2次,即可到达a所在过程的sp。,28,5.3 栈式动态分配,如何在调用序列中生成访问链 建立访问链的代码是过程调用序列的一部分; 假定嵌套深度为np的过程p调用嵌套深度为nx的过程x,则建立访问链分下边两种情况: npnx:必有: np-nx=-1 此时被调用过程x的访问链必须指向栈中刚好在它下面的调用过程p活动记录的访问链(无需追踪访问链)。 npnx:p和x有公共外层,即深度为1,2,.(nx-1)的过程 从调用过程追踪访问链np-nx1次,到达静态包围x和p的最接近过程的最
16、新活动记录,所到达的访问链就是被调用过程x必须指向的访问链。 结论,追踪访问链次数: np-nx+1,29,5.3 栈式动态分配,利用显示表(display)访问非本地数据 任何一个深度为i的活动,它可以访问的本地与非本地数据只能是嵌套深度不大于i的、最新被调用的活动。换句话说,在嵌套层次小于等于当前活动嵌套层次的活动中,每层仅有一个(本层最新)活动记录可以被访问到。,30,5.3 栈式动态分配,结论: 可以访问几个非本地的活动记录由静态嵌套深度确定; 可以访问哪几个非本地的活动记录由动态的运行轨迹确定 同一深度的活动在控制栈中满足LIFO的原则。利用访问链访问非本地数据的弱点:若当前活动的过
17、程嵌套深度为n,则需要追踪访问链n-i次(i=1, 2, ., n-1),才能找到所需活动记录的sp。 解决思路:当前活动记录直接访问每层最新活动记录的sp 具体方法:显示表display,一个以嵌套深度为下标的数组,每个元素是一个栈性质的单链表,连接此嵌套深度的、在生存期的所有活动记录(sp),最新活动记录在表头。,31,5.3 栈式动态分配,若嵌套深度为np的过程p 引用嵌套深度为na(nanp)的变量a,则a的活动记录的访问链地址可以在dna中找到,访问非本地变量a的地址信息表示为:(na, a) 访问S中的a: dsp=d1 adsp,32,5.3 栈式动态分配,如何生成与维护显示表及
18、其访问链 在沿访问链追踪的访问模式中,访问链只需建立,无需撤销,因为活动记录的撤销自然也撤销了访问链。 若采用显示表方式,则当进入和退出活动时,均需对显示表进行维护,以保证对非本地数据的正确访问(弱点)。 活动记录中访问链的右值: 访问链方式:指向最新直接外层活动记录的sp 显示表方式:指向同层次的次新活动记录的sp 调用时: 将di的值保存在新活动记录的访问链中; 置di为新的活动记录。 返回时:恢复di原来的值,即上述1的逆操作,作业,P259 5.5,33,34,5.4 本章小结,本章介绍程序运行时的空间组织,重点是讨论如何通过对过程的静态分析(包括符号表的使用)建立运行环境,以保证程序的正确执行。 过程的动态特性 过程、活动、生存期、控制流; 活动树、控制栈、活动记录; 名字的绑定、环境与状态; 运行时的存储空间组织 内存划分:可执行代码、静态数据区、栈、堆; 活动记录的内容:参数与返回值、控制链、访问链、机器状态、本地数据、临时变量等。,35,5.4 本章小结,不同的存储分配策略 静态分配:绑定是静态的 栈分配:基于控制栈自动管理内存 堆分配:根据要求从堆数据区动态地分配和释放内存 栈分配策略 调用序列与返回序列; 控制链与访问链:控制链用于活动记录的正确切换,体现活动调用关系;访问链用于访问非本地数据,体现过程嵌套关系; 访问链的实现:访问链、显示表;,