过程:过程、函数等程序单元
活动(activation):过程的一次执行
活动记录:过程的每次活动中,存储所需的局部数据的一块连续的存储区
活动的生存期(lifetime):从过程体开始执行到执行结束的时间,包括执行被其调用的过程所需的时间,以及这样的过程调用过程所花的时间等
6.1 局部存储管理
6.1.1 过程
过程定义←过程名→过程体
有返回值的过程称为函数
形式参数:过程定义中出现的参数
实在参数:调用语句中出现的参数
6.1.2 名字的作用域和绑定
作用域规则:指定一个过程调用在程序中出现时,哪个声明映射到此次调用的规则。
作用域:一个声明起作用的程序部分称为该声明的作用域。
如果过程中出现的名字映射到的声明在这个过程中,则称该出现局部于该过程,否则为非局部的。
环境:将名字映射到存储单元的函数。
状态:将存储单元映射到它所保存值的函数。
如果环境将名字x映射到存储单元s,就说x被绑定(binding)到s。
6.1.3 活动记录
帧(frame):过程一次执行所需局部信息所在的一块连续的存储区,叫做活动记录的帧。一个帧常常由如下部分构成:
- 临时数据:保存临时值
- 局部数据:保存过程的局部数据
- 保存的机器状态:保存的过程调用前的机器状态信息,包括EIP,ESP等
- 访问链:有些语言需要用访问链来访问非局部数据。
- 控制链:用来指向调用者的活动记录。
- 参数:调用过程提供的实在参数。常常用寄存器传递参数。
- 返回值:用于存放被调用过程返回给调用过程的值。通常也用寄存器返回。
6.1.4 局部数据的安排
局部数据的地址用相对于活动记录中某个位置的相对地址表示,按照变量出现的次序,在局部数据区依次分配空间。活动记录中其他域的访问都可采用相对地址(偏移)的方式处理。
受到机器寻址的限制,常常在存储安排的时候还要考虑对齐,存放在内存中满足一定条件的位置,如按字对齐,就要求变量地址的最低位为0。由于考虑对齐而引起的无用空间叫做衬垫区。
6.1.5 程序块
程序块(block):过程中本身含有局部变量声明的语句。程序块可以有嵌套结构。在过程中需要为内嵌程序块预留空间。作用域互相不重叠的程序块中的局部变量,可以放在同一块区域。嵌套的程序块不会影响局部数据域大小的确定。
6.2 全局栈式存储分配
6.2.1 运行时内存的划分
┏━━━━━━━━━┓
┃ 代 码 ┃
┠─────────┨
┃ 静态区 ┃
┠─────────┨
┃ 堆 ┃
┠────┬────┨
┃ ↓ ┃
┃ 空闲内存 ┃
┃ ↑ ┃
┠────┴────┨
┃ 栈 ┃
┗━━━━━━━━━┛
在实际机器上,栈向低地址方向增长,堆向高地址方向增长。
6.2.2 活动树和运行栈
对于常见的过程语言,两个过程活动的生存期或嵌套,或无重叠。因此,可以用树来表示进入和离开活动的次序,这样的树称为活动树。活动树的结点和活动一一对应。过程的执行过程对应树的遍历。活动开始:先序遍历。活动结束:后续遍历。
当前活跃的活动在活动树上总是一条路径。可以存储到栈中,称为控制栈。如果向控制栈中增加过程活动的活动记录,则称为运行栈。每次过程调用——被调用过程活动的活动记录入栈,调用结束——出栈。
6.2.3 调用序列
- 过程调用序列:在过程调用时执行的分配活动记录,以及把信息填入其域中的代码。
- 过程返回序列:在过程返回时执行的恢复机器状态,回收活动记录,以及让调用过程继续执行的代码。
设计原则:
- 调用者和被调用者之间交流的数据一般放在被调用者活动记录的开始处,并尽可能靠近调用者的活动记录。
- 固定长度的想通常放在活动记录的中间,包括控制链、访问链和机器状态域。
- 在编译时不能及时知道大小的一些想放在活动记录的末端。
过程调用序列中,调用者负责将参数和返回值的空间压栈,然后跳转到被调用者,被调用者负责保存机器状态、建立控制链,并分配局部数据存储空间。
过程返回序列中,被调用者先保存返回值,然后恢复EBP的值,并恢复机器状态,跳转回调用者,主调者再取出返回值。
6.2.4 栈上可变长度数据
通过偏移指针的形式访问,定长的指针放在局部数据域,数据放在活动记录的末端。
6.2.5 悬空引用
引用某个已被回收的存储单元叫做悬空引用。是一种逻辑错误,程序员应自己避免。
6.3 非局部名字的访问
- 静态作用域规则:仅根据程序正文静态地确定用于名字的声明。
- 动态作用域规则:在运行时根据当前存活的过程活动来确定用于名字的声明。
6.3.1 无过程无嵌套的静态作用域
无过程嵌套,非局部变量一定是外部变量,放在静态存储区固定的位置。
6.3.2 有过程嵌套的静态作用域
有过程嵌套时,非局部变量可能存在于运行栈上。需要根据嵌套深度建立访问链(又称静态链)。访问链指向的是最靠近该活动的属于该过程活动记录的访问链。根据所需变量的嵌套深度,追踪访问链,达到变量所在过程的活动记录。
6.3.3 动态作用域
被调用过程可以访问所有调用它的祖先的局部变量。
- 深访问:用控制链作为访问链——牺牲时间,换取空间
- 浅访问:局部变量全部放在静态数据区——牺牲空间,换取时间
6.4 参数传递
6.4.1 值调用
- 把形参当作所在过程的局部名看待,形参的存储单元在该过程的活动记录中。
- 调用者计算实参,并把它的值放入形参的存储单元中。
6.4.2 引用调用(地址调用)
调用者把实参存储单元的地址传给被调用者,被调用者对形参的任何访问就是对对应实参的访问。
6.4.3 换名调用
内联函数,直接展开,不修改活动栈
6.5 堆管理
6.5.1 内存管理器
内存管理器负责堆上的空间的分配和回收,是应用程序和操作系统之间的接口。执行的基本函数是分配函数和回收函数。
内存管理器应具有:
6.5.2 计算机内存分层
存储器层次结构。寄存器——高速缓存——内存——磁盘,速度递减,容量递增。
编译器只管理寄存器,其他由操作系统进行管理。
6.5.3 程序局部性
动态调整缓存的内容
编译器将毗邻执行的基本块尽可能放在同一页中
6.5.4 手工回收请求
常见错误:
- 内存泄漏错误:没有释放已经引用不到的堆块
- 悬空引用错误:引用已经被释放的堆块