第六章 运行时储存空间的组织和管理

过程和函数这样的程序单元统称为过程,程序运行时过程的一次执行称为过程的一次活动。过程的每次活动都需要可执行代码和存放所需数据的储存空间,所需的局部数据通常用一块连续的储存区来存放,称之为活动记录。过程p一次活动的生存期是从过程体开始执行到执行结束的时间,包括消耗在执行被p调用的过程所需的时间,以及再由这样的过程调用过程所花的时间等。

  • 过程的活动
    过程的一次执行称为过程的一次活动
  • 活动记录
    过程的活动需要可执行代码和存放所需信息的存储空间,后者称为活动记录
  • 影响存储分配策略的语言特征
    • 过程能否递归
    • 当控制从过程的活动返回时,局部变量的值是否要保留
    • 过程能否访问非局部变量
    • 过程调用的参数传递方式
    • 过程能否作为参数被传递
    • 过程能否作为结果值传递
    • 存储块能否在程序控制下动态地分配
    • 存储块是否必须显式地释放

      6.1 局部存储分配策略

      6.1.1 过程

      过程定义、过程调用、形式参数、实在参数、活动的生存期

      6.1.2 名字的作用域和绑定

      名字的作用域
  • 一个声明起作用的程序部分称为该声明的作用域
  • 即使一个名字在程序中只声明一次,该名字在程序运行时也可能表示不同的数据对象

    6.1.3 活动记录

    6.1.4 局部数据的安排

  • 字节是可编址内存的最小单位
  • 变量所需的存储空间可以根据其类型而静态确定
  • 一个过程所声明的局部变量,按这些变量声明时出现的次序,在局部数据域中依次分配空间
  • 局部数据的地址可以用相对于某个位置的地址来表示
  • 数据对象的存储安排还有一个对齐问题

    6.1.5 程序块

  • 本身含有局部变量声明的语句
  • 可以嵌套
  • 最接近的嵌套作用域规则
  • 并列程序块不会同时活跃
  • 并列程序块的变量可以重叠分配

    6.2 全局存储分配策略

  • 介绍程序运行时所需的各个活动记录在存储空间的分配策略
  • 描述过程的目标代码怎样访问绑定到局部名字的存储单元
  • 介绍三种分配策略
    • 静态分配策略
    • 栈式分配策略
    • 堆式分配策略

      6.2.1 运行时内存的划分

      6.2.2 静态分配

  • 名字在程序被编译时绑定到存储单元,不需要运行时的任何支持
  • 绑定的生存期是程序的整个运行期间
  • 控制再次进入该过程时,局部变量的值和控制上一次离开时的一样
  • 每个活动记录的大小是固定的
  • 过程调用时保存信息的地址在编译时也是已知的
  • 静态分配给语言带来限制
    • 递归过程不被允许
    • 数据对象的长度和它在内存中位置的限制,必须是在编译时可以知道的
    • 数据结构不能动态建立

      6.2.3 栈式分配

  • 活动树的特点
    • 每个结点代表某过程的一个活动
    • 根结点代表主程序的活动
    • 结点a是结点b的父结点,当且仅当控制流从a的活动进入b的活动
    • 结点a处于结点b的左边,当且仅当a的生存期先于b的生存期
  • 运行栈:把控制栈中的信息拓广到包括过程活动所需的所有局部信息(即活动记录)
  • 过程调用和过程返回都需要执行一些代码来管理活动记录栈,保存或恢复机器状态等
  • 过程调用序列
    过程调用时执行的分配活动记录,把信息填入它的域中的代码
  • 过程返回序列
    过程返回时执行的恢复机器状态,释放活动记录,使调用过程能够继续执行的代码
  • 调用序列和返回序列常常都分成两部分,分处于调用过程和被调用过程中
  • 即使是同一种语言,过程调用序列、返回序列和活动记录中各域的排放次序,也会因实现而异
  • 设计这些序列和活动记录的一些原则是
    • 以活动记录中间的某个位置作为基地址
    • 长度能较早确定的域放在活动记录的中间
    • 一般把临时数据域放在局部数据域的后面
    • 把参数域和可能有的返回值域放在紧靠调用者活动记录的地方
    • 用同样的代码来执行各个活动的保存和恢复
  • 活动记录的长度在编译时不能确定的情况
    • 局部数组的大小要等到过程激活时才能确定
    • 在活动记录中为这样的数组分别存放数组指针的单元
    • 运行时,这些指针指向分配在栈顶的存储空间

      6.3 非局部名字的访问

      6.3.1 无过程嵌套的静态作用域

  • 过程体中的非局部引用可以直接使用静态确定的地址
  • 局部变量在栈顶的活动记录中,可以通过base_sp指针来访问
  • 无须深入栈中取数据,无须访问链
  • 过程可以作为参数来传递,也可以作为结果来返回

    6.3.2 有过程嵌套的静态作用域

    6.3.3 动态作用域

  • 被调用过程的非局部名字a和它在调用过程中引用的是同样的存储单元
  • 新的绑定仅为被调用过程的局部名字建立,这些名字在被调用过程的活动记录中占用的存储单元
  • 实现动态作用域的方法
    • 深访问
      用控制链搜索运行栈,寻找包含该非局部名字的第一个活动记录
    • 浅访问
      • 为每个名字在静态分配的存储空间中保存它的当前值
      • 当过程p的新活动出现时,p的局部名字n使用在静态数据区分配给n的存储单元。n的先前值可以保存在p的活动记录中,当p的活动结束时再恢复

        6.4.2 引用调用

  • 实参的左值传给被调用过程
  • 引用调用可以如下实现:
    • 把实参的左值放入形参的存储单元
    • 在被调用过程的目标代码中,任何对形参的引用都是通过传给该过程的指针来间接引用实参的

      6.4.3 复写-恢复调用

  • 值调用和引用调用的混合
  • 复写-恢复调用可以如下实现:
    • 实参的右值和左值同时传给被调用过程
    • 在被调用过程中,像值调用那样使用实参的右值
    • 当控制返回调用过程时,根据传递来的实参的左值,将形参当前的值复写到实参存储单元

      6.4.4 换名调用