本文共 7470 字,大约阅读时间需要 24 分钟。
程序运行时的存储组织及管理概述
- 在生成目标代码之前,需要对变量或形参进行相对地址的分配(分配的结果存入符号表中),以便生成具有变址访问的目标代码
- 例如:
ADD AX, X[sp]
其中 X X X代表偏移量, s p sp sp 代表数据区起始地址。该目标程序在运行时进行绝对地址的分配
- 程序运行时的存储区
- 程序运行时,操作系统将为程序分配一块存储区,用于被编译过的程序的运行
- 从用途上看,这块空间可分为以下几个部分:
- 目标程序区:用来存放目标代码。目标代码所需要的空间大小在编译时就可以确定,因此目标程序区属于静态区域
- 静态数据区:用来存放编译时就能确定存储空间的数据
- 运行栈区:用来存放运行时才能确定存储空间的数据
- 运行堆区:用来存放运行时用户动态申请存储空间的数据

堆区与栈区可分配在内存的两端并相向增长
需要存储的对象分类
- 持久生命周期的对象
- 程序代码(动态连接的部分除外)
- 源程序中的常量以及全局变量和静态变量等
- 短暂生命周期的对象,可以用栈来为这类变量分配存储空间
- 生命周期长短由用户决定的对象 (动态变量)
静态存储区
- 存储生命周期和整个程序执行时间相等的对象
- 静态存储区的特点:
- 其分配确定后直到程序执行完之前都不会修改。该区可细分为目标代码区、常数区、静态数据区和库代码区
- 一般而言,程序代码和常量都不能修改,所以目标代码区、常数区和库代码区通常是只读区。静态数据区是可读可写区
- 分配在静态存储区的每个对象地址在编译的连接装配阶段就可以确定
动态存储区
- 存储生命周期和其所属的函数执行时间相等或由用户决定的对象
- 动态存储区的特点:
- 在程序执行过程中各对象的存储位置会不断地修改
- 动态区可以细分为栈区(Stack)和堆区(Heap)
- 栈区用于存储非静态局部变量的值
- 分配在堆中的对象分配到存储空间和释放所分到的存储空间的次序是任意的。堆用于存储动态变量的值
早期的 FORTRAN 语言在编译时完全可以确定程序所需的所有数据空间,因此,存储空间只有目标程序区和静态数据区。C、PASCAL 这样的语言,在编译时不能完全确定程序所需要的数据空间,因此,需要采用动态存储分配
静态存储分配
- 对于源程序中出现的各种数据项,必须在编译时就知道它们的存储空间的大小,在编译时就给它们分配固定的存储空间,而且在目标程序运行时,总是使用这些在编译时就分配好的存储单元作为它们的数据空间
- 采用静态存储分配的语言必须满足下列条件:
- 不允许过程有递归调用
- 不允许有可变大小的数据项,如可变数组或可变字符串
- 不允许用户动态建立数据实体
实现静态存储分配
- 编译程序对源程序进行处理时,对每个变量在符号表中创建一个记录,保存该变量的属性,其中包括为变量分配的存储空间地址即目标地址
- 由于每个变量需要的空间大小已知,则可将数据区开始位置的地址 A A A 分配给第一个变量,设第一个变量占 n 1 n_1 n1 个字节,则 A + n 1 A+n_1 A+n1 分配给第二个变量。同理, A + n 1 + n 2 A+n_1+n_2 A+n1+n2 分配给第三个变量等等
- 静态存储分配把内存看成一个一维数组。 编译时分配存储单元,运行时才被占用 。一旦分配,运行期间就一直被某个变量占用
- 目标地址可以是绝对地址,也可以是相对地址
- 开始如果编写的编译程序是用于单任务环境,那么,通常采用绝对地址作为目标地址。 程序和数据区 (开始地址 A A A) 可以放在操作系统的长驻区以外的存储区中
- 如果编译程序是在多任务环境中,那么目标地址可采用相对地址,也就是相对于程序数据区的基地址。加载器通过设置一个寄存器,并将数据区的头地址送入该寄存器内以完成数据区的定位
临时变量的地址分配
- 临时变量的的作用域是层次嵌套的 (栈),作用域不相交,则可以公用一个存储单元
- 大部分临时变量名用来存放表达式的中间结果,这类变量名有一个特点,它们均只被定值一次,被引用一次
例
- 对赋值语句
X := A * B – C * D + E * F
产生的临时变量的处理 
- “代真”后的四元式序列

简单栈式动态存储
简单栈式存储分配:过程不允许嵌套定义,允许递归调用,如C语言
动态存储分配
- 有些语言允许有长度可变的串、动态数组,并允许过程递归调用,每个目标所需要的数据区的大小在编译时是不知道的,那么在编译时就无法确定数据空间的具体大小,故其存储分配必须留到目标程序运行时动态地进行。动态存储分配分为栈式和堆式
栈式动态存储分配
- 当运行中进入一个程序模块时(例如函数被调用),该模块所需的数据区大小必须是已知的。类似地,数据目标的多次出现也是允许的。运行时,每当进入一个程序块,就为其分配一个新的数据区
- 栈式动态存储分配策略适合于块结构语言。
- 栈式动态存储分配策略是将整个程序的数据空间设计为一个栈(称为运行时栈)
- 每当程序调用一个过程(进入一个块)时,就在栈顶为被调过程分配一个新的数据空间
- 保留块的开始地址:基地址寄存器(BASE,或用 sp)
- 为当前块分配存储:在栈顶留出相应单元,作为当前数据区
- 为每个局部量分配相对地址,填入符号表( a d r adr adr属性)。 相对地址为相对于该块数据区开始地址的位移量
- 块内变量 x x x 的运行时地址: B A S E + o f f s e t ( x ) BASE+offset(x) BASE+offset(x)
- 当被调过程结束时(退出块),则释放栈顶的这部分空间
- 恢复当前块的开始地址。 t o p = s p − 1 top= sp-1 top=sp−1
- s p = 老 s p sp=老sp sp=老sp

活动记录( A R AR AR)(activity record)
- 子程序(函数或过程)的一次执行,称为子程序的一次活动
- 对于 C 语言,函数是程序块,一对花括号内的程序即复合语句也可以看成是一个程序块
- 当进入一个程序模块时,就在运行栈的栈顶创建一个专用存储块,称为活动记录
- 包含该程序模块内定义的局部变量所必需的存储空间、全局数据区指针以及形式参数区、隐式参数等(不包括全局变量、静态变量等)
- 过程的每一个形参及局部变量在活动记录中的偏移量是已知的,在编译的时候已经填入符号表中,为目标代码生成作准备 绝 对 地 址 = 活 动 记 录 基 地 址 ( s p ) + 相 对 地 址 绝对地址=活动记录基地址(sp)+相对地址 绝对地址=活动记录基地址(sp)+相对地址
- 当该模块执行结束时(即到达模块的出口),其相应的活动记录将从运行栈的栈顶删除。因此在该模块中所定义的变量在该模块的外部是不存在的
现行活动记录
- 每个过程可能有若干个不同的活动记录,代表了不同的调用。将最新的活动记录称为现行(现役)活动记录
- 例如,以下为递归实现的阶乘程序运行时活动记录的示意图

活动记录结构
- 控制链(可选):指向主调过程的活动记录的起始位置
- 存取链(可选):指向本活动要访问的非局部数据所在活动记录的起始位置
- 机器状态:容纳该过程执行前(调用过程活动在调用点)的机器状态信息。包括程序计数器(PC)、各种寄存器的值等
- 局部数据:子程序定义的局部变量
- 临时数据:存储子表达式值

箭头表示栈向下生长
- S p Sp Sp 和 T o p Top Top 指针:
- S p Sp Sp 是基地址指针,指向当前最新的活动记录的起点
- T o p Top Top 指向当前最新的活动记录的顶端
- 老 S p 老Sp 老Sp:指向调用该过程的主调过程的最新的活动记录的开始位置

- 动态链(又称为控制链):栈中数据区之间由 老 s p 老sp 老sp 形成一条动态链,反映在运行中过程之间的相互调用关系
- 子程序的动态链不唯一
- 例: P i − 1 P_{i-1} Pi−1 调用 P i P_i Pi,如果 P i P_i Pi 没有非正常出口,则 P i P_i Pi 将返回到 P i − 1 P_{i-1} Pi−1 中

函数定义语句的翻译:
- 函数的名称、层次号、返回值类型、函数体的入口地址、形参个数、形参及局部变量的名称和类型、形参及局部变量的偏移量、数据区的长度填入函数符号表
- 设置未来栈中新数据区首尾指针的目标代码
- 带有变址访问的函数体目标代码
- 过程体中,凡引用形参、局部变量或数据元素 x x x,都可根据相对过程活动记录起点的偏移量来访问 s p + o f f s e t ( x ) sp+offset(x) sp+offset(x)
过程调用应完成的工作
- 过程语句
P(T1,T1,…,Tn)
的目标代码(调用序列)完成如下任务: - 在符号表中检查函数名及形实参匹配情况 (语义分析)
- 调用者对实在参数求值,并把它们传送给被调用过程的活动记录的参数域
- 调用者在被调用者的活动记录中存放返回地址和老的 s p sp sp 值。之后调用者修改 s p sp sp 值
- 被调用者存放寄存器值和其它状态信息
- 被调用者在栈顶为局部变量分配数据区
- 开始执行(转向过程体)
- 调用语句
call P(T1,T2,…,Tn)
翻译成四元式中间代码 P a r T 1 P a r T 2 … . P a r T n c a l l P , n \begin{aligned}Par \ \ T_1\\ Par\ \ T_2\\ ….\\Par\ \ T_n\\ call\ \ P, n\end{aligned} Par T1Par T2….Par Tncall P,n - 每个 P a r T i Par\ \ T_i Par Ti 完成实参到形参的传递。 ( i + 3 ) [ t o p ] = T i (i+3)[top]= T_i (i+3)[top]=Ti (因为前3个位置分别存 老 s p sp sp、返回地址、参数个数)
- c a l l P , n call\ \ P , n call P,n 完成如下的任务:
- 1 [ t o p ] = s p 1[top]=sp 1[top]=sp :保护现行 s p sp sp (成为老 s p sp sp)
- 2 [ t o p ] = p c 2[top]=pc 2[top]=pc
- 3 [ t o p ] = n 3[top]=n 3[top]=n :传递参数个数
- J S R P JSR\ \ P JSR P:进入过程体 (修改指令计数器 p c pc pc)
例
c a l l P ( a + b , c ) call\ \ P(a+b,c) call P(a+b,c) 对应四元式
100 t = a + b 101 par t 102 par c 103 call P, 2
进入过程体的任务
sp = top + 1 //设置新活动记录的sptop = top + L //设置新活动记录的top,L是数据区的长度
过程返回,return
目标代码完成的任务是
- 被调用者在自己的活动记录的返回值域中放一个返回值 (可以送到寄存器中,等待调用段取走)
- 利用状态域中的信息,被调用者恢复 s p sp sp 和其它寄存器;并且按返回地址转移到调用者的代码中
- 调用者复制返回值到自己的活动记录中
// 恢复工作top = sp - 1 // 恢复top sp = 0[sp] // 恢复spX = 2[top] // 得到主调过程的返回地址J 0[X] // 无条件返回到主调过程
嵌套过程语言的栈式实现
过程允许嵌套定义,允许递归调用,如 PASCAL 语言
- 嵌套定义函数时,有 层数 的概念

- 一个过程可以引用包围它的任一外层过程定义的变量 (非局部量),因此在运行时必须知道它的所有外层过程的最新活动纪录的地址,需要跟踪每个外层过程的最新活动纪录的位置
- 嵌套层次显示表 display 表
- 存取链(静态链)
display 表方法
- display 表可看成指针数组(存地址):
- d i s p l a y [ k ] , d i s p l a y [ k − 1 ] , … , d i s p l a y [ 0 ] display[k], display[k-1], …, display[0] display[k],display[k−1],…,display[0] 依次存放着现行层、直接外层,…,直至最外层的每一个过程的最新活动记录的起始地址(基地址)
- 每当一个过程 R R R 被调用的时候,就为其建立一个 display 表;方法是找到其直接外层过程的活动记录的 display 表,拷贝 I R I_R IR 层 ( I R I_R IR 为过程 R R R 的层数),再填上现行层的基地址

- 例1:程序嵌套关系如图所示,给出当 P → S → Q → R P→S → Q → R P→S→Q→R 时的运行栈及 R R R 的 display 表

- 例2:程序嵌套关系如图所示,给出当 S → Q 1 → Q 2 → P S→Q^1 → Q^2 → P S→Q1→Q2→P 时的运行栈及 P P P 的 display表

- 非局部变量在运行栈上的绝对地址 绝 对 地 址 = d i s p l a y [ 静 态 层 数 ] + 相 对 地 址 绝对地址=display[静态层数]+相对地址 绝对地址=display[静态层数]+相对地址
- d i s p l a y [ 静 态 层 数 ] display[静态层数] display[静态层数] 得到非局部变量所在层的活动记录的基地址
- 相对地址:非局部变量在其活动记录中的相对地址
- 伪码:现行过程中引用了某一外层过程(层数为 k k k)获得 X X X 值的方法
LD R1, (d + k)[SP] // 获得第 k 层过程的最新活动纪录LD R2, X[R1] // 把 X 的值传送到 R2
引入 DISPLAY 表以后的活动纪录

- 全局 display 为一个指针,指向其直接外层过程的活动记录的 display 表
- display 为当前活动记录的 display 表
- 因此,连接数据包含 3 项
- 老 S P SP SP 值;返回地址;全局 D I S P L A Y DISPLAY DISPLAY 地址
过程调用,过程进入
- 由于增设了一个连接数据,所以每个 p a r T i par\ \ T_i par Ti 相应的指令改为: ( i + 4 ) [ T O P ] : = T i ( i + 4) [TOP] := T_i (i+4)[TOP]:=Ti
- c a l l P , n call\ \ P, n call P,n 翻译成 1 [ T O P ] : = S P ; 3 [ T O P ] : = S P + d ; 4 [ T O P ] : = n ; J S R P \begin{aligned}1 [TOP] &:= SP;\\ 3 [TOP] &:= SP + d;\\ 4 [TOP] &:= n;\\ JS&R \ \ P\end{aligned} 1[TOP]3[TOP]4[TOP]JS:=SP;:=SP+d;:=n;R P
- 接下来
- 定义新的 S P , T O P SP , TOP SP,TOP
- 保护返回地址
- 按第三项连接数据所提供的全局 DISPLAY 的地址,自低向上抄录 I I I 个单元的内容( I I I 为 P P P 的层数),再添上新的 S P SP SP 以形成新的 DISPLAY ( I + 1 I + 1 I+1 个单元)
- P P P 返回 T O P : = S P − 1 ; S P : = 0 [ S P ] ; X : = 2 [ T O P ] ; U J 0 [ X ] ; \begin{aligned}TOP &:= SP -1 ; \\ SP &:= 0 [SP]; \\ X &:= 2 [TOP]; \\ UJ&\ \ 0 [X];\end{aligned} TOPSPXUJ:=SP−1;:=0[SP];:=2[TOP]; 0[X];
存取链(静态链)方法
- 在活动记录中增加一项称为存取链的指针,使其指向直接嵌套外层的最新活动记录的开始位置,存取链始终记录着程序静态定义时该过程所有的直接外层

参数传递
- 如果实参是一个简单变量,数组元素或者临时变量,则根据传地址或者传值,之前对 p a r T par\ \ T par T 的解释是正确的
- 如果实参为数组,过程或者标号, p a r T par\ \ T par T 的作用都是传地址,而且在传地址之前要做别的工作
- p a r T par\ \ T par T, T T T 为数组:若要求运行时对形、实数组的维数一致性和体积相容性进行动态检查,则应传送 T T T 的内情向量;否则,只传送 T T T 的首地址
- p a r T par\ \ T par T, T T T 为过程
- 例子:假定过程 P P P 把过程 T T T 作为实参传递给过程 Q Q Q,随后, Q Q Q 又通过引用相应的形式参数调用 T T T,调用关系如图

- P P P 和 T T T 仅有的两种关系
- P P P 的 DISPLAY 正好就是 T T T 的直接外层的 DISPLAY

- P P P 的 DISPLAY 包含了 T T T 的直接外层的 DISPLAY

- 不论何种情况,假定 T T T 的层数为 l l l,那么, T T T 的 DISPLAY 是由 P P P 的 DISPLAY 的前 l l l 个单元的内容和 S P SP SP 的现行值组成
传值调用
- 一个形参被处理成如同一个局部变量一样,因此这个形参存储在被调用过程的活动记录中
- 调用过程中计算实在参数,并把它们的实际值存入形式参数的存储地址中
引用调用
- 把实在参数的地址传递给相应的形式参数。
- 在目标代码中,在被调用过程中对形式参数的一次引用就成为对传递给被调用过程的指针的一个间接引用
转载地址:http://weih.baihongyu.com/