1.3 程序单元
为了后面讨论程序结构和语言实现问题,在此引入程序单元(Program Unit)和单元实例(Unit Instance)的概念。
在程序设计语言中存在一些实体,例如FORTRAN语言的子程序和ALGOL 60语言的分程序。它们作为程序执行过程中的独立调用单位,称为程序单元。在不引起混淆的情况下,简称单元(Unit)。通常,单元可以独立开发,有的语言(如FORTRAN)允许程序单元独立编译,然后把若干个编译好的单元组合起来,成为一个完整的可执行程序。一个程序中的若干个程序单元在程序运行时依照控制流程逐一被激活(Activation)。
在编译时,一个单元的源程序称为单元表示(Unit Representation);在运行时,一个单元表示由一个代码段(Code Segment)和一个活动记录(Activation Record)组成,此时称单元表示为单元实例。程序单元可以看成一个抽象的概念,程序运行时对它赋予具体的代码段和活动记录,也就构成了一个单元实例。
代码段的内容是单元所具有的指令,这些可执行的指令对程序单元的每一个单元实例都是不变的。所谓活动记录就是包含执行这个单元所必需的信息,以及该单元的局部变量(Local Variable)所绑定的数据对象的存储区。活动记录的内容是可变的。数据对象在活动记录中的相对位置称为位移(Offset),活动记录所占存储单元个数称为活动记录的长度。局部变量在程序单元中是可见的。
程序单元可以命名,也可不命名。Pascal的过程与函数和C语言的函数是命名的程序单元,ALGOL 60语言的分程序是不命名的程序单元。程序单元不是孤立的,即不一定是一个完全独立的程序。若一个程序单元是子程序,那么它可由别的程序单元通过子程序调用来激活并开始执行,执行后返回调用点,因此返回位置是必须保留的信息。子程序被调用时将建立并激活该子程序的一个实例,其返回地址保存在这个实例的活动记录中。另外,在语言作用域规则允许的前提下,一个程序单元可以引用未被自己说明而由其他单元说明的变量,这种变量称为非局部变量(Nonlocal Variable)。在一个程序中,各个程序单元都可以引用的变量称为全局变量(Global Variable)。
一个程序单元可以引用哪些变量呢?按照上述定义,可以引用局部变量和非局部变量。我们把一个程序单元U可以引用的局部变量和非局部变量定义为程序单元U的引用环境(Referencing Environment)。局部变量绑定于存储在U的当前活动记录中的数据对象,它被称为局部环境(Local Environment)。非局部变量绑定于别的(说明该非局部变量)程序单元的活动记录中的数据对象,它被称为非局部环境(Nonlocal Environment)。显然,对程序单元U来说,引用环境中的变量是可见和可以访问的,其他变量均是不可见和不可以访问的。若一个程序单元的引用环境中有两个变量绑定于同一个数据对象,则称这些变量具有别名(Alias)。当对绑定的一个非局部变量进行修改时,将产生副作用(参看3.3.1节)。
程序单元可以被递归地激活。当一个程序单元自己调用自己时,产生直接递归(Direct Recursion)。当一个程序单元调用别的程序单元,再由别的程序单元调用这个程序单元时,产生间接递归(Indirect Recursion)。当一个程序单元被递归激活,即它的上次活动尚未终止,又再次被激活,此时它的前一个活动记录尚未释放而又产生一个新的活动记录。按照程序单元实例的定义可知,一个程序单元可能具有多个实例。同一程序单元的不同实例的代码段是相同的,所不同的仅仅是活动记录。因此,在递归激活的情况下,活动记录与它的代码段之间的绑定必须是动态的。每次激活一个程序单元,必须完成活动记录与其代码段之间的绑定,形成一个单元实例。
某些语言,例如FORTRAN,不支持单元的递归激活,因此这类语言的单元实例最多只能有一个,即只有一个代码段和一个活动记录。这类语言程序的单元实例代码段与活动记录之间的绑定是静态的。单元局部变量的数据对象的初始化(建立)可以在程序执行之前完成,即可静态实现分配。这类语言又称静态语言(Static Language)(参见第13.2节)。