China-pub.com 下载 第7章 运行时环境 本章要点 ·程序执行时的存储器组织 ·动态存储 ·完全静态运行时环境 ·参数传递机制 ·基于栈的运行时环境 ·TNY语言的运行时环境 在前几章中,我们已研究了实现源语言静态分析的编译程序各阶段。该内容包括了扫描 分析和静态语义分析。这个分析仅仅取决于源语言的特性,它与目标(汇编或机器)语言及目标 机器和它的操作系统的特性完全无关。 在本章及下一章中,我们将转向研究编译程序如何生成可执行代码的问题。这个研究包括 了附加分析,例如由优化程序实现的分析,其中的一些可以与机器无关。但是代码生成的许 多任务都依赖于具体的目标机器。然而同样地代码生成的一般特征在体系结构上仍保留了很 大的变化。运行时环境(runtime environment)尤为如此,运行时环境指的是目标计算机的寄存 器以及存储器的结构,用来管理存储器并保存指导执行过程所需的信息。实际上,几乎所有 的程序设计语言都使用运行时环境的3个类型中的某一个,它的主要结构并不依赖于目标机器 的特定细节。环境的这3个类型分别是:FORTRAN77的完全静态环境(fully static environment) 特征、像C、C++、Pascall以及Ada这些语言的基于栈的环境(stack-based environment),以及 像LISP这样的函数语言的完全动态环境(fully dynamic environment))。这3种类型的混合形式 也是可能的。 本章将按顺序逐个讨论这3种环境,还指出哪些环境是可行的语言特征以及它们必须具有 的特性。这包括了作用域及分配问题、过程调用的本质和不同的参数传递机制。这一章集中讨 论的是环境的一般结构,而第8章着重于维护环境需要生成的真实代码。在这一点上,大家应 记住编译程序只能间接地维护环境,在程序执行期间它必须生成代码进行必要的维护操作。相 反地由于解释程序可以在其自己的数据结构中直接维护环境,因而它的任务就很简单。 本章的第一节包括了对所有运行时环境的一般特征及其与目标机器的体系结构之间的关系 的论述:之后的两节探讨了静态环境和基于栈的环境,以及执行时的操作示例。由于基于栈的 环境是最常见的,所以我们对于基于栈系统的不同变型和结构又要着重讲述。在这之后是一些 动态存储问题,其中包括了完全动态环境和面向对象的环境。下面还会讲到有关环境操作的名 种参数传递技术。本章最后简要描述了实现TNY语言所需的简单环境。 7.1程序执行时的存储器组织 典型计算机的存储器可分为寄存器区域和较慢的直接编址的随机访问存储器(RAM)。 RAM区域还可再分为代码区和数据区。在绝大多数的语言中,执行时不可能改变代码区,且 在概念上可将代码和数据区看作是独立的。另外由于代码区在执行之前是固定,所以在编译时 所有代码的地址都是可计算的,代码区可如下所示:
下载 第7章 运行时环境 本章要点 • 程序执行时的存储器组织 • 动态存储 • 完全静态运行时环境 • 参数传递机制 • 基于栈的运行时环境 • TINY语言的运行时环境 在前几章中,我们已研究了实现源语言静态分析的编译程序各阶段。该内容包括了扫描、 分析和静态语义分析。这个分析仅仅取决于源语言的特性,它与目标 (汇编或机器)语言及目标 机器和它的操作系统的特性完全无关。 在本章及下一章中,我们将转向研究编译程序如何生成可执行代码的问题。这个研究包括 了附加分析,例如由优化程序实现的分析,其中的一些可以与机器无关。但是代码生成的许 多任务都依赖于具体的目标机器。然而同样地代码生成的一般特征在体系结构上仍保留了很 大的变化。运行时环境 (runtime environment)尤为如此,运行时环境指的是目标计算机的寄存 器以及存储器的结构,用来管理存储器并保存指导执行过程所需的信息。实际上,几乎所有 的程序设计语言都使用运行时环境的 3个类型中的某一个,它的主要结构并不依赖于目标机器 的特定细节。环境的这3个类型分别是:F O RT R A N 7 7的完全静态环境(fully static environment) 特征、像C、C + +、P a s c a l以及A d a这些语言的基于栈的环境 (stack-based environment),以及 像L I S P这样的函数语言的完全动态环境 (fully dynamic environment)。这3种类型的混合形式 也是可能的。 本章将按顺序逐个讨论这 3种环境,还指出哪些环境是可行的语言特征以及它们必须具有 的特性。这包括了作用域及分配问题、过程调用的本质和不同的参数传递机制。这一章集中讨 论的是环境的一般结构,而第 8章着重于维护环境需要生成的真实代码。在这一点上,大家应 记住编译程序只能间接地维护环境,在程序执行期间它必须生成代码进行必要的维护操作。相 反地由于解释程序可以在其自己的数据结构中直接维护环境,因而它的任务就很简单。 本章的第一节包括了对所有运行时环境的一般特征及其与目标机器的体系结构之间的关系 的论述;之后的两节探讨了静态环境和基于栈的环境,以及执行时的操作示例。由于基于栈的 环境是最常见的,所以我们对于基于栈系统的不同变型和结构又要着重讲述。在这之后是一些 动态存储问题,其中包括了完全动态环境和面向对象的环境。下面还会讲到有关环境操作的各 种参数传递技术。本章最后简要描述了实现 T I N Y语言所需的简单环境。 7.1 程序执行时的存储器组织 典型计算机的存储器可分为寄存器区域和较慢的直接编址的随机访问存储器 ( R A M )。 R A M区域还可再分为代码区和数据区。在绝大多数的语言中,执行时不可能改变代码区,且 在概念上可将代码和数据区看作是独立的。另外由于代码区在执行之前是固定,所以在编译时 所有代码的地址都是可计算的,代码区可如下所示:
China-pub.com 第7章运行时环境 267 下载 过程1的入口点 过程的代码 过程2的入口点 过程2的代码 过程m的入口点 过程n的代码 代码存储器 特别地,在编译时还可以知道每个过程的入口点和函数⊙。对数据的分配不能这样说,它只有 小部分可在执行之前被分配到存储器中的固定位置。本章大部分内容都会谈论如何处理非固 定的或动态的数据分配。 在执行之前,可以将一类数据固定在存储器中,它还包括了程序的全局和/或静态数据 (FORTRAN77与绝大多数的语言不同,它所有的数据都属于这一类)。这些数据通常都在一个 固定区域内并以相似的风格单独分配给代码。在Pascal中,全局变量属于这一类,C的外部和 静态变量也是如此 在组织全局/静态区域中出现的一个问题是它涉及到编译时所知的常量。这其中包括了C和 Pascal的consti声明以及代码本身所用的文字值,例如串“Hel1。名d\i和在C语句 printf (Bello sd\n "12345 ) 中的整型值12345。诸如0和1这样较小的编译时常量通常由编译程序直接插入到代码中且不为 其分配任何数据空间。同样地,由于编译程序已掌握了全局函数或过程的入口点且可直接将其 插入到代码中,所以也不为它们分配全局数据区。然而我们却将大型的整型值、浮点值,特别 是串文字分配到全局静态区域中的存储器 在启动时仅保存一次,之后再由执行代码从这些 位置中得到(实际上,在C中串文字被看作是指针,因此它们必须按照这种方式来保存)。 用作动态数据分配的存储区可按多种方式组织。典型的组织是将这个存储器分为栈(stak) 区域和堆(heap)区域,栈区域用于其分配发生在后进先出LIFO(last--in,irst-out)风格中的数据 而堆区域则用于不符合LFO协议(例如在C中的指针分配)的动态分配©。目标机器的体系结构通 常包括处理器栈,利用了这个栈使得用处理器支持过程调用和返回(使用基于存储器分配的主要 机制)成为可能。有时,编译程序不得不将处理器栈的显式分配安排在存储器内的恰当位置中。 一种一般的运行时存储器组织如下所示,它具.有上术所有的程储娶分类: 代码区城 全程/静态区域 空间 日更为可能的情况是,代码由装载程序装载到存储器的一个在执行开始时分配的区域中,因此这是完全不可预 测的。但是之后的所有定际地址都由从固定的势载基地址的馆移自动计位组出,因出和固定地址的原理相同 有时,编译器的编写者必须留心生成可重定位代码(relocatable code,其中都相对于某个基址(base(通常是寄 存器)执行转移、调用以及引用。下一章将给出一些例子 读者应注意到堆通常是 个简单的线性存储器区域。将它称之为堆是一个历史原因,这与算法(如堆类排序) 中用到的堆数据结构无关
特别地,在编译时还可以知道每个过程的入口点和函数 。对数据的分配不能这样说,它只有 一小部分可在执行之前被分配到存储器中的固定位置。本章大部分内容都会谈论如何处理非固 定的或动态的数据分配。 在执行之前,可以将一类数据固定在存储器中,它还包括了程序的全局和 /或静态数据 ( F O RT R A N 7 7与绝大多数的语言不同,它所有的数据都属于这一类 )。这些数据通常都在一个 固定区域内并以相似的风格单独分配给代码。在 Pascal 中,全局变量属于这一类, C的外部和 静态变量也是如此。 在组织全局/静态区域中出现的一个问题是它涉及到编译时所知的常量。这其中包括了 C和 Pascal 的c o n s t声明以及代码本身所用的文字值,例如串“Hello %d\n”和在C语句 printf (" Hello %d\n ", 12345 ) ; 中的整型值1 2 3 4 5。诸如0和1这样较小的编译时常量通常由编译程序直接插入到代码中且不为 其分配任何数据空间。同样地,由于编译程序已掌握了全局函数或过程的入口点且可直接将其 插入到代码中,所以也不为它们分配全局数据区。然而我们却将大型的整型值、浮点值,特别 是串文字分配到全局 /静态区域中的存储器,在启动时仅保存一次,之后再由执行代码从这些 位置中得到(实际上,在C中串文字被看作是指针,因此它们必须按照这种方式来保存 )。 用作动态数据分配的存储区可按多种方式组织。典型的组织是将这个存储器分为栈 ( s t a c k ) 区域和堆( h e a p )区域,栈区域用于其分配发生在后进先出 LIFO(last-in, first-out)风格中的数据, 而堆区域则用于不符合L I F O协议(例如在C中的指针分配)的动态分配 。目标机器的体系结构通 常包括处理器栈,利用了这个栈使得用处理器支持过程调用和返回(使用基于存储器分配的主要 机制)成为可能。有时,编译程序不得不将处理器栈的显式分配安排在存储器内的恰当位置中。 一种一般的运行时存储器组织如下所示,它具有上述所有的存储器分类: 代码区域 全程 / 静态区域 栈 自由空间 堆 第 7章 运行时环境 2 6 7 下载 过程1的入口点 过程1的代码 过程2的代码 过程n的代码 代码存储器 过程2的入口点 过程n的入口点 更为可能的情况是,代码由装载程序装载到存储器的一个在执行开始时分配的区域中,因此这是完全不可预 测的。但是之后的所有实际地址都由从固定的装载基地址的偏移自动计算得出,因此和固定地址的原理相同。 有时,编译器的编写者必须留心生成可重定位代码 (relocatable code),其中都相对于某个基址( b a s e ) (通常是寄 存器)执行转移、调用以及引用。下一章将给出一些例子。 读者应注意到堆通常是一个简单的线性存储器区域。将它称之为堆是一个历史原因,这与算法 (如堆类排序) 中用到的堆数据结构无关。 ↓ ↑
268 翁译原理及实践 China-pub.com 下载 上图中的箭头表示栈和堆的生长方向。传统上是将栈画作在存储器中向下生长,这样它的 顶部实际就是在其所画区域的底部。堆也画得与栈相似,但它不是LFO结构且它的生长和缩 短比箭头所表示的还要复杂(参见7.4节)。在某些组织中,栈和堆被分配在不同的存储器部分, 而不是占据相同的区域。 存储器分配中的 一个重要单元是过程活动记录(procedure activation record),当调用或激活 过程或函数时,它包含了为其局部数据分配的存储器。活动记录至少应包括以下几个部分: 白变量《参数)空问 图鞋起合色的空它布T西 用作局部数据的空间 用作局部临时变量的空间 在这里应福调而日以后环要重复议个图示仅仅表示的是活动记最的一般组织。句括其所含影 据的顺序的特定细节则依赖于目标机器的体系结构、被编译的语言特性,甚至还有编译程序的 编写者的喜好。 所有过程活动记录的某些部分,例如用于簿记信息的空间,具有相同的大小。而其他部分 诸如用于自变量和局部数据的空间会对每一个过程保持固定,但是每个过程都各不相同。某些 活动记录还会由处理器自动分配到过程调用上(例如存储返回地址)。其他部分(如局部临时变量 空间)可能需要由编译程序生成的指令显式地分配。根据语言的不同,可能将活动记录分配在 静态区域(FORTRAN77)、栈区域(C、Pascal)、或堆区域(LISP)。当将活动记录保存在栈中时, 它们有时指的是栈框架(stack frame)。 处理器寄存器也是运行时环境的结构部分。寄存器可用来保存临时变量、局部变量甚至是 全局变量。当处理器具有多个寄存器时,正如在较新的RISC处理器中一样,整个静态区域和 整个活动记录都可完整地保存在寄存器中。处理器还具有特殊用途的寄存器以记录执行,如在 大多数的体系结构中的程序计数器(pc、栈指针(sp)(stack pointer)。可能还会为跟踪过程活动 而特别设计寄存器。这样的寄存器典型的有指向当前活动记录的框架指针(仰)((frame pointer), 以及指向保存自变量(参数值)的活动记录区域的自变量指针(argument pointer)e。 运行时环境的一个特别重要的部分是当调用过程或函数时,对必须发生的操作序列的判定 这样的操作可能还包括活动记录的存储器分配、计算和保存自变量以及为了使调用有效而进行 的必要的寄存器的保存和设置。这些操作通常指的是调用序列(calling sequence)。过程或函数 返回时需要的额外操作,如放置可由调用程序访问的返回值、寄存器的重新调整,以及活动记 录存储器的释放,也通常被认为是调用序列的一个部分。如果需要,可将调用时执行的调用序 列部分称作是调用序列(call sequence),而返回时执行的部分称为返回序列(return sequence)。 调用序列设计的重要方面有:1)如何在调用程序和被调用程序之间分开调用序列操作(也 就是有多少调用序列的代码放在调用点上,多少放在每个过程的代码开头):2)在多大程度上 依赖处理器对调用支持而不是为调用序列的每一步生成显式代码。由于在调用点上比在被调用 ⊙这些名称都是从V八X体系结构中得到的,但是类似的名称还可出现在其他体系结构中
上图中的箭头表示栈和堆的生长方向。传统上是将栈画作在存储器中向下生长,这样它的 顶部实际就是在其所画区域的底部。堆也画得与栈相似,但它不是 L I F O结构且它的生长和缩 短比箭头所表示的还要复杂 (参见7 . 4节)。在某些组织中,栈和堆被分配在不同的存储器部分, 而不是占据相同的区域。 存储器分配中的一个重要单元是过程活动记录(procedure activation record),当调用或激活 过程或函数时,它包含了为其局部数据分配的存储器。活动记录至少应包括以下几个部分: 在这里应强调(而且以后还要重复)这个图示仅仅表示的是活动记录的一般组织。包括其所含数 据的顺序的特定细节则依赖于目标机器的体系结构、被编译的语言特性,甚至还有编译程序的 编写者的喜好。 所有过程活动记录的某些部分,例如用于簿记信息的空间,具有相同的大小。而其他部分, 诸如用于自变量和局部数据的空间会对每一个过程保持固定,但是每个过程都各不相同。某些 活动记录还会由处理器自动分配到过程调用上 (例如存储返回地址)。其他部分(如局部临时变量 空间)可能需要由编译程序生成的指令显式地分配。根据语言的不同,可能将活动记录分配在 静态区域( F O RT R A N 7 7 )、栈区域( C、P a s c a l )、或堆区域( L I S P )。当将活动记录保存在栈中时, 它们有时指的是栈框架(stack frame)。 处理器寄存器也是运行时环境的结构部分。寄存器可用来保存临时变量、局部变量甚至是 全局变量。当处理器具有多个寄存器时,正如在较新的 R I S C处理器中一样,整个静态区域和 整个活动记录都可完整地保存在寄存器中。处理器还具有特殊用途的寄存器以记录执行,如在 大多数的体系结构中的程序计数器 ( p c )、栈指针(sp)(stack pointer)。可能还会为跟踪过程活动 而特别设计寄存器。这样的寄存器典型的有指向当前活动记录的框架指针 (fp)(frame pointer), 以及指向保存自变量(参数值)的活动记录区域的自变量指针( a rgument pointer) 。 运行时环境的一个特别重要的部分是当调用过程或函数时,对必须发生的操作序列的判定。 这样的操作可能还包括活动记录的存储器分配、计算和保存自变量以及为了使调用有效而进行 的必要的寄存器的保存和设置。这些操作通常指的是调用序列 (calling sequence)。过程或函数 返回时需要的额外操作,如放置可由调用程序访问的返回值、寄存器的重新调整,以及活动记 录存储器的释放,也通常被认为是调用序列的一个部分。如果需要,可将调用时执行的调用序 列部分称作是调用序列(call sequence),而返回时执行的部分称为返回序列(return sequence)。 调用序列设计的重要方面有: 1) 如何在调用程序和被调用程序之间分开调用序列操作 (也 就是有多少调用序列的代码放在调用点上,多少放在每个过程的代码开头 );2 )在多大程度上 依赖处理器对调用支持而不是为调用序列的每一步生成显式代码。由于在调用点上比在被调用 2 6 8 编译原理及实践 下载 自变量(参数)空间 用作薄记信息的空间,它包括了返 回地址 用作局部数据的空间 用作局部临时变量的空间 这些名称都是从VA X体系结构中得到的,但是类似的名称还可出现在其他体系结构中
China-pub.Com 269 下载 第7章运行时环境 程序内更易生成调用序列代码,但是这样做会引起生成代码数量的生长,因为在每个调用点都 要复制相同的代码所以第1点是一个很棘手的问题,后面还要再更详细地讲到这些问题。 调用程序至少要负责计算自变量并将它们放在可由被调用程序找到的位置上(可能是直接 放在被调用程序的活动记录中)。此外,调用程序或被调用程序或两者必须保存调用点上的机 器状态,包括返回地址,可能还有正使用的寄存器。最后,在调用程序和被调用程序之间须用 某个可能的合作方式建立所有附加的簿记信息。 7.2完全静态运行时环境 最简单的运行时环境类型是所有数据都是静态的,且执行程序期间在存储器中保持固定。 这样的环境可用来实现没有指针或动态分配,且过程不可递归调用的语音。此类语言的标准倒 子是FORTRAN77。 在完全静态环境中,不仅全局变量,所有的变量都是静态分配。因此,每个过程只有一个 在执行之前被静态分配的活动记录。我们都可通过固定的地址直接访问所有的变量,而不论它 们是局部的还是全局的,则整个程序存储器如下所示: 主过程的代码 过程1的代码 代绿 过程n的代码 会品数摇风城 主过程的活动记录 过程1的活动记录 过程m的话动记录 在这样的环境中,保留每个活动记录的簿记信息开销相对较小,而且也不需要在活动记录中 保存有关环境的额外信息(而不是返回地址)。用于这样环境的调用序列也十分简单。当调用 一个过程时,就计算每个自变量,并将其保存到被调用过程的活动中恰当的参数位置。接着 保存调用程序代码中的返回地址,并转移到被调用的过程的代码开头。返回时,转移到返回 地址©。 例7.1作为这种环境的具体示例,考虑程序清单7-1中的FORTRAN77程序。这个程序有一个 主过程和一个附加的过程QUADMEAN。在主过程和QUADMEAN中有由COMMON MAXSIZE声明 当执行返回指令时,也自动地再装载这个地址 我们忽略库函数SQRT ,它由QUADMEANI调用而且在执行之前先被链接
程序内更易生成调用序列代码,但是这样做会引起生成代码数量的生长,因为在每个调用点都 要复制相同的代码所以第1点是一个很棘手的问题,后面还要再更详细地讲到这些问题。 调用程序至少要负责计算自变量并将它们放在可由被调用程序找到的位置上 (可能是直接 放在被调用程序的活动记录中 )。此外,调用程序或被调用程序或两者必须保存调用点上的机 器状态,包括返回地址,可能还有正使用的寄存器。最后,在调用程序和被调用程序之间须用 某个可能的合作方式建立所有附加的簿记信息。 7.2 完全静态运行时环境 最简单的运行时环境类型是所有数据都是静态的,且执行程序期间在存储器中保持固定。 这样的环境可用来实现没有指针或动态分配,且过程不可递归调用的语言。此类语言的标准例 子是F O RT R A N 7 7。 在完全静态环境中,不仅全局变量,所有的变量都是静态分配。因此,每个过程只有一个 在执行之前被静态分配的活动记录。我们都可通过固定的地址直接访问所有的变量,而不论它 们是局部的还是全局的,则整个程序存储器如下所示: 在这样的环境中,保留每个活动记录的簿记信息开销相对较小,而且也不需要在活动记录中 保存有关环境的额外信息 (而不是返回地址 )。用于这样环境的调用序列也十分简单。当调用 一个过程时,就计算每个自变量,并将其保存到被调用过程的活动中恰当的参数位置。接着 保存调用程序代码中的返回地址,并转移到被调用的过程的代码开头。返回时,转移到返回 地址 。 例7.1 作为这种环境的具体示例,考虑程序清单 7 - 1中的F O RT R A N 7 7程序。这个程序有一个 主过程和一个附加的过程Q U A D M E A N 。在主过程和Q U A D M E A N中有由COMMON MAXSIZE声明 第 7章 运行时环境 2 6 9 下载 在大多数的体系结构中,子例程转移自动地保存返回地址;当执行返回指令时,也自动地再装载这个地址。 我们忽略库函数S Q RT,它由Q U A D M E A N调用而且在执行之前先被链接。 代码 区域 主过程的代码 过程1的代码 过程n 的代码 全局数据区域 数据 区域 主过程的活动记录 过程n 的活动记录 过程1的活动记录
270 翁译原理及实践 China-pub.C 下载 给出的全局变量⊙。 程序清单7-1一个FORTRAN77示例程序 D日02BMp里gT COMMON MAXS:工ZE ).TEMP RRAD TABLE(1),TABLE(2),TABLE(3) (TABLE,3,TEMP) DHEAN (A,SIZE,QHEAN) REAL A(S江ZE),QE,TP IF ((SIZE.GT.MAXSIZE).OR.(SIZE.L.1))GOTO 99 D010K·1,8江z TEMP A(K)"A(K) 39() RETURN END 忽略存储器中整型值和浮点值间可能有的大 小区别,我们显示了图7-1中的这个程序的运 全局区 MAXSIZE 行时环境⑨。在该图中,我们用箭头表示从 TABLE 主过程中调用时,过程QUADMEAN的参数A (10) SIZE和OMEAN的值。在FORTRAN77中,参 数值是隐含的存储引用,所以调用(TAB工E、 TEMP 3和TEMP)的参数地址就被复制到 3 QUADMEAN的参数地址中。它有几个后果。 A 首先,需要一个额外的复引用来访问参数 812班 值。其次,数组参数无需再重新设置和复 OMEAN 制(因此,只给在QUADMEAN中的数组参数 A分配一个空间,在调用时指出TABLE的基 的活动记录 返国地址 地址)。再次,像在调用中的值3的常量参数 四P 必须被放在一个存储器地址中而日在调用 时要使用这个地址(7.5节将更完整地讨论参 数传递机制)。 图71中还有一个特性需要解释一下,这 图7-!程序清单7-1中程序的运行时环境 日实际上,FORTRAN77允许COMMON变量在不同的过程中具有不同的名称,而仍旧指的是相同存储器位置。 从这个示例开始,我们将默认忽略这种复杂性」 ©我们再次强调这个图示仅仅是示意性的。在操作中的实现实际与这里给出的有所差异
给出的全局变量 。 程序清单7-1 一个F O RT R A N 7 7示例程序 忽略存储器中整型值和浮点值间可能有的大 小区别,我们显示了图 7 - 1中的这个程序的运 行时环境 。在该图中,我们用箭头表示从 主过程中调用时,过程 Q U A D M E A N的参数A、 S I Z E和Q M E A N的值。在F O RT R A N 7 7中,参 数值是隐含的存储引用,所以调用 (T A B L E、 3 和 T E M P ) 的 参 数 地 址 就 被 复 制 到 Q U A D M E A N的参数地址中。它有几个后果。 首先,需要一个额外的复引用来访问参数 值。其次,数组参数无需再重新设置和复 制(因此,只给在 Q U A D M E A N中的数组参数 A分配一个空间,在调用时指出 T A B L E的基 地址)。再次,像在调用中的值 3的常量参数 必须被放在一个存储器地址中而且在调用 时要使用这个地址 ( 7 . 5节将更完整地讨论参 数传递机制 )。 图7 - 1中还有一个特性需要解释一下,这 2 7 0 编译原理及实践 下载 实际上,F O RT R A N 7 7允许C O M M O N变量在不同的过程中具有不同的名称,而仍旧指的是相同存储器位置。 从这个示例开始,我们将默认忽略这种复杂性。 我们再次强调这个图示仅仅是示意性的。在操作中的实现实际与这里给出的有所差异。 全局区 主过程的 活动记录 过程Q U A D M E A N 的活动记录 QMEAN 返回地址 图7-1 程序清单7 - 1中程序的运行时环境