ch6.运行时刻的环境
程序设计语言基础
嵌套结构语言
允许在过程内部又说明过程(函数和过程,可以统一称作过程/子程序)
以PSACAL语言为例,PSACAL语言的特点如下:
1、最近嵌套作用域原则:一个名字的作用域是那个包含了这个名字的说明的最小过程或函数
2、过程/函数允许嵌套:允许在一个过程或函数的内部又说明过程或函数,内层可以引用外层过程中说明的名字
在PASCAL语言中,子程序调用规则如下:
1、外层可以调用内层,但不能隔层调用
2、内层可以调用外层,且可以隔层调用
3、任何过程或函数都不能调用主程序
4、先说明的过程可以调用后说明的过程,但必须加向前引用的说明
过程的活动
过程的活动:过程的一次执行。每执行一个过程体,就产生该过程的一个活动
过程的数据区/活动记录:用于管理过程在一次执行中所需要的信息所使用的一个连续的存储块
不同程序之间的活动记录的内容和顺序可能有所不同
一般情况下,活动记录按照从用户栈的低地址到高地址的顺序,应包含如下内容:返回地址、形式单元、局部变量、内情向量和临时变量。其中,栈指针指向返回地址
存储组织与分配概述
存储组织
在编译阶段,编译程序会组织源程序在运行阶段的存储空间,这些存储空间包括:用户定义的变量与常量,临时工作单元,过程或函数调用时需要的连接单元与返回地址,参数传递,与操作系统的接口,输入/输出所需的缓冲区
在运行阶段,随着目标代码的运行,数据的存储组织形式得以实现
典型的存储组织与分配方案
静态存储分配
在编译阶段,对源程序中的量分配以固定单元,运行时始终不变
静态存储分配要求:
1、程序中的每一个数据对象的大小,在编译阶段都能够确定,即不应有可变数组等可变体积的数据
2、不允许有递归等能够导致每个数据对象在某个时刻存在多个实例的结构
3、数据的性质在运行前就应确定
在静态存储分配时,存储器具有如下结构:
静态存储分配,在过程活动结束后,仍能够保留过程中的局部量的值
动态存储分配
有些程序语言的类型是不需要声明的,这样的变量所占存储空间大小,在编译时无法确定,属于动态数据。这些动态数据的存储分配,在运行阶段时动态地进行
动态分配包括栈式存储分配和堆式存储分配
在运行阶段的存储空间,在编译阶段都需要进行组织
存储组织与分配需解决的问题
标识符的作用域问题
对于全局变量而言,在编译时就可以映射到存储单元
而对于局部量而言,只有在过程/函数被调用时才会存在。在编译时,确定每个局部量占用存储空间的大小,以及相对于过程/函数数据区首地址的偏移量。同时,也应该对局部量的地址对齐问题进行考虑
对于具有嵌套结构的程序而言,调用时如何访问非局部变量,也是一个需要解决的问题
参数的传递方式及其实现
实在参数(实参)如何与形式参数(形参)相关联?
临时单元的分配
在活动记录中,一般把临时数据域放在局部数据域的后面
过程或函数调用时需要的连接单元与返回地址
过程调用开始时:
1、调用者进行形参、实参的代换
2、调用者建立参数表、连接数据等
3、被调用者保存状态信息、初始化局部数据,并转换过程体起始地址
过程调用结束时:
1、被调用者参数返回
2、被调用者恢复调用者的数据区并将返回地址送PSW
3、调用者从断点处继续执行
栈式存储组织与分配
基本思想
每调用一次过程,就将相应的数据区放置在栈顶,调用完后再进行释放
存储分配实现时需要解决的问题
临时变量、数组等特殊量的存储分配:临时变量直接在数据区中分配。数组只在数据区中安排内情向量所需单元,实际分量则安排在运行时分配
调用结束的返回地址:每个被调用者都在相应的数据区内加上返回地址项
嵌套过程之间对非局部量的访问:在被调用者数据区开辟单元存放嵌套过程间联系信息
栈式存储分配时的活动记录
其中,控制链指向调用者的活动记录。访问链则是在被调用过程需要其他地方的某个数据时用来进行定位
非局部量的访问
嵌套深度的定义:如果一个过程p在一个嵌套深度为i的过程中定义,则P的嵌套深度为i+1。在不能嵌套定义的C语言中,所有函数的嵌套深度为1
之前提到的访问链,或者说是静态链,是用来指向其直接外层最新活动记录的指针
此外,还有一个嵌套层次显示表,其本质是指针数组。这里面的指针依次指向当前层、直接外层、……,直至最外层(0层,主程序层)等每一层过程的最新活动记录的首地址
而前面所提到的控制链,或者说是动态链,则是指向调用者活动记录的首地址
访问非局部量时,会根据层差的值,来决定沿着静态链跳转的次数,从而找到外层中的非局部量