第七章 子程序控制
第七章 子 程 序 控 制
主要讨论子程序间的交互,特别是如何以结构的、高效的方式 传递数据。 71子程序顺序控制 简单的子程序“调用一返回” 程序可视为以子程序为单元的层次结构 主程序 子程序 子程序 子程序.」程序 子程序..子程序 子程序调用的控制机制可用“拷贝”规则来解释,子程序调 用语句的效果可通过下面方式同样得到:执行用子程序体的 拷贝来替代调用语句(合适地替换参数和冲突标识符)。 从这个观点看,子程序调用可视为这样一种控制结构,它使 得不必要在程序的多个地方拷贝大量相同或几乎相同的语句
主要讨论子程序间的交互,特别是如何以结构的、高效的方式 传递数据。 7.1 子程序顺序控制 简单的子程序“调用—返回” 程序可视为以子程序为单元的层次结构 主程序 子程序 … 子程序 子程序…子程序 子程序…子程序 子程序调用的控制机制可用“拷贝”规则来解释,子程序调 用语句的效果可通过下面方式同样得到:执行用子程序体的 拷贝来替代调用语句(合适地替换参数和冲突标识符)。 从这个观点看,子程序调用可视为这样一种控制结构,它使 得不必要在程序的多个地方拷贝大量相同或几乎相同的语句
这个观点中有若干隐含假设,如果适当放松可得到更一般的 子程序控制结构。 1、子程序不能是递归的 递归分为直接递归和间接递归。 对非递归子程序调用,在翻译时,原理上我们可以应用 拷贝规则 但如子程序是直接递归的,则拷贝规则在原理上也是不 可能使用的,因为替代过程不会终止,替代一个cl1少 引入一个cal间接递归可允许删去某些子程序,但最终 仍将带来其他的直接递归 2、显式的调用语句是需要的。 因为需要替代,必须有显式的替代点。 而对例外处理这一类子程序,则不存在显式的调用点
这个观点中有若干隐含假设,如果适当放松可得到更一般的 子程序控制结构。 1、子程序不能是递归的 递归分为直接递归和间接递归。 对非递归子程序调用,在翻译时,原理上我们可以应用 拷贝规则。 但如子程序是直接递归的,则拷贝规则在原理上也是不 可能使用的,因为替代过程不会终止,替代一个call至少 引入一个call。间接递归可允许删去某些子程序,但最终 仍将带来其他的直接递归。 2、显式的调用语句是需要的。 因为需要替代,必须有显式的替代点。 而对例外处理这一类子程序,则不存在显式的调用点
3、子程序必须在每次调用中被完整地执行。 拷贝规则应用后,则子程序需从头执行到尾 但对协同例程子程序,可能在其终止点后继续执行 4、在调用点立即转换控制权 显式的调用和拷贝规则,使得程序控制权被立即转移 但对被调度子程序的调用,子程序的执行可能被延迟一 定时候。 5、单个执行序列 在执行中某点,只有一个子程序拥有控制权,执行是单 序列的。 如我们停止程序则我们总可以知道是哪个子程序拥有控 制权,哪些程序的执行被挂起,哪些还未被调用,哪些 已结束执行。 但用作任务的子程序可以并发执行。 Fortran基本上是以拷贝规则来看待子程序调用的
3、子程序必须在每次调用中被完整地执行。 拷贝规则应用后,则子程序需从头执行到尾。 但对协同例程子程序,可能在其终止点后继续执行。 4、在调用点立即转换控制权。 显式的调用和拷贝规则,使得程序控制权被立即转移。 但对被调度子程序的调用,子程序的执行可能被延迟一 定时候。 5、单个执行序列。 在执行中某点,只有一个子程序拥有控制权,执行是单 序列的。 如我们停止程序则我们总可以知道是哪个子程序拥有控 制权,哪些程序的执行被挂起,哪些还未被调用,哪些 已结束执行。 但用作任务的子程序可以并发执行。 Fortran基本上是以拷贝规则来看待子程序调用的
简单的调用一返回结构 实现 首先需建立完整的程序执行模型。对表达式或语句序列,被 视为运行时的可执行代码块。表达式或语句序列的执行即是 代码块的执行,通过硬件解释器进行。 但对子程序而言,我们需要更多 1、在子程序定乂(翻译为模板)和子程序激活(调用时 创建)间存在不同。 2、激活实现为两部分,代码段十激活记录 3、代码段在执行过程中不变,子程序的每个激活使用相 同代码段。 4、激活在调用时创建,返回时消除,激活中的内容会因 赋值而经常变化
简单的调用—返回结构 •实现 首先需建立完整的程序执行模型。对表达式或语句序列,被 视为运行时的可执行代码块。表达式或语句序列的执行即是 代码块的执行,通过硬件解释器进行。 但对子程序而言,我们需要更多: 1、在子程序定义(翻译为模板)和子程序激活(调用时 创建)间存在不同。 2、激活实现为两部分,代码段十激活记录。 3、代码段在执行过程中不变,子程序的每个激活使用相 同代码段。 4、激活在调用时创建,返回时消除,激活中的内容会因 赋值而经常变化
为避免混淆,我们不是简单地说子程序中某特定语句的执行, 而是说:在子程序的激活R中S的执行。 这样,为了跟踪程序执行点,我们需要两片数据,用于存放 两个系统定义的指针变量。 当前指令指针 CIP指向当前正在被硬件或软件解释器执行的指令。 当前环境指针 因为同一子程序的所有激活使用同一代码段,只知道当 前指令是不够的,还需要指向激活记录(代表了子程序 的引用环境)的指针(因此称为环境指针) CEP—当前环境指针
为避免混淆,我们不是简单地说子程序中某特定语句的执行, 而是说:在子程序的激活R中S的执行。 这样,为了跟踪程序执行点,我们需要两片数据,用于存放 两个系统定义的指针变量。 •当前指令指针 CIP指向当前正在被硬件或软件解释器执行的指令。 •当前环境指针 因为同一子程序的所有激活使用同一代码段,只知道当 前指令是不够 的,还需要指向激活记录(代表了子程序 的引用环境)的指针(因此称为环境指针)。 CEP——当前环境指针
CEP和CIP的配合,控制了程序的执行路线。 为了保证从子程序的正确返回,CIP和CEP需合适地存放。 通常在调用时将CIP和CEP存放在被调用子程序的激活记 录中 在激活记录中包含一个系统定义的数据对象—返回 点(存放两个指针值)。 当遇到cal调用时,将旧的(jp,ep)存放到激活记录中返 回点,将新的(ip,ep)赋给CIP、CEP,从而完成控制的 转移 遇到返回语句时,取旧的(ip,ep),重设置CIP,CEP, 返回控制权。 图71,给出了一个主程序,两个子程序执行的例子
CEP和CIP的配合,控制了程序的执行路线。 为了保证从子程序的正确返回,CIP和CEP需合适地存放。 通常在调用时将CIP和CEP存放在被调用子程序的激活记 录中。 在激活记录中包含一个系统定义的数据对象——返回 点(存放两个指针值)。 当遇到call调用时,将旧的(ip, ep)存放到激活记录中返 回点,将新的(ip, ep)赋给CIP、CEP,从而完成控制的 转移。 遇到返回语句时,取旧的(ip, ep),重设置CIP,CEP, 返回控制权。 图7.1,给出了一个主程序,两个子程序执行的例子
Main program Subprogram A Subprogram B code CALL A 子程序B开始执行时的执行状态 CALL B eturn segment Constants CALL B Return CALL A Constants R1 R2 R3 11 Return point R1 R2 (ip, ep) Local Local Loca Dynamic variables variables variables activation etc CIP: 15<- System defined pointer variables CEP
子程序B开始执行时的执行状态
这个实现子程序调用返回的模型通常足以作为以后讨论 的几个子程序控制结构变体的基础。 子程序的拷贝规则观点的重要性质是:在程序执行中任 点,最多只有某子程序的一个激活是被使用的。子程 序可以多次调用,但在其下一次激活开始时,每个激活 必须完成且终止。 基于这个性质,可导出一个更简单的子程序实现模型, 只要我们愿意用存储来增加执行速度 作法: 为每个子程序静态地分配用以存放单个激活记录的空 间,作为代码数的扩展,而不是运行时创建 很多 Fortran和 COBOL实现采用这种方式
这个实现子程序调用返回的模型通常足以作为以后讨论 的几个子程序控制结构变体的基础。 子程序的拷贝规则观点的重要性质是:在程序执行中任 一点,最多只有某子程序的一个激活是被使用的。子程 序可以多次调用,但在其下一次激活开始时,每个激活 必须完成且终止。 基于这个性质,可导出一个更简单的子程序实现模型, 只要我们愿意用存储来增加执行速度。 •作法: 为每个子程序静态地分配用以存放单个激活记录的空 间,作为代码数的扩展,而不是运行时创建。 很多Fortran和COBOL实现采用这种方式
这样执行不需动态分配空间,而是重复地使用同一个 激活记录,只是在调用发生时对其进行初始化而已 这种模型也带来其他简化,CEP不再需要,因为当前 激活记录总是CIP指定的代码数的扩展。 对调用一返回的更一般实现,底层硬件很少能提供帮助 然而,对简化的实现,硬件常提供- return-Jump指令,允 许子程序调用被单条硬件指令所实现。这样CIP直接表示 为硬件的地址寄存器。 返回跳指令将地址寄存器中内容存放到内存或寄存器中, 并为其赋新值 图72是一个例子
这样执行不需动态分配空间,而是重复地使用同一个 激活记录,只是在调用发生时对其进行初始化而已。 这种模型也带来其他简化,CEP不再需要,因为当前 激活记录总是CIP指定的代码数的扩展。 对调用—返回的更一般实现,底层硬件很少能提供帮助。 然而,对简化的实现,硬件常提供一return-jump指令,允 许子程序调用被单条硬件指令所实现。这样CIP直接表示 为硬件的地址寄存器。 返回跳指令将地址寄存器中内容存放到内存或寄存器中, 并为其赋新值。 图7.2是一个例子