浅谈静态作用域和动态作用域

静态作用域和动态作用域

所谓的作用域就是指某段程序文本代码。一个声明起作用的那一段程序文本区域,则称为这个声明的作用域。静态作用域是指声明的作用域是根据程序正文在编译时就确定的,有时也称为词法作用域。而在采用动态作用域的语言中,程序中某个变量所引用的对象是在程序运行时刻根据程序的控制流信息来确定的。

 

大多数现在程序设计语言都是采用静态作用域规则,而只有为数不多的几种语言采用动态作用域规则,包括APL、Snobol和Lisp的早期方言。而采用静态作用域的语言中,基本都是最内嵌套作用域规则:由一个声明引进的标识符在这个声明所在的作用域里可见,而且在其内部嵌套的每个作用域里也可见,除非它被嵌套于内部的对同名标识符的另一个声明所掩盖。为了找到某个给定的标识符所引用的对象,应该在当前最内层作用域里查找。如果找到了一个声明,也就可以找到该标识符所引用的对象。否则我们就到直接的外层作用域里去查找,并继续向外顺序地检查外层作用域,直到到达程序的最外嵌套层次,也就是全局对象声明所在的作用域。如果在所有层次上都没有找到有关声明,那么这个程序就有错误。

 

设想标识符x出现在某个函数体中,而x又不是在该函数体内定义的,那么x的值必然依赖于该函数外部的某个声明。那么在程序运行时,程序要查找标识符x所引用的对象则必须依据该语言所采用的作用域规则。静态作用域和动态作用域的一个重要区别在于:静态作用域规则查找一个变量声明时依赖的是源程序中块之间的静态关系;而动态作用域规则依赖的是程序执行时的函数调用顺序。说的具体点,就是静态作用域查找的是距离当前作用域最近的外层作用域中同名标识符的声明,而动态作用域则是查找最近的活动记录(关于活动记录见我的上一篇随笔)中的同名标识符声明。下面我们通过一个小例子来解释这点。

 

下面是一小段C语言代码(我们这里假设不同的声明在不同的块,也对应着每个声明在不同的活动记录中)。

1 int x = 1;
2 int g(int z) { return x + z; }
3 int f(int y)
4 {
5 int x = y + 1;
6 return g(y*x);
7 }
8 f(3);

调用f(3)会引起函数f内部调用g(12),于是函数g的定义中的表达式x+z会被执行。调用g之后,控制栈中的活动记录包括最外层x的定义、对函数f的调用和对g的调用,如图1所示。

 

图 1

此时控制栈中有两个名为x的变量,一个在最外层作用域中定义,另一个是函数f的局部变量。在采用动态作用域规则的情况下,表达式x+z中的x会从最近的活动记录中寻找变量x的值,也即函数调用f(3)的活动记录中的x值,则x的值4;而如果采用静态作用域规则,表达式x+z中的变量x则从函数g定义所在作用域的外层作用域中寻找,此时x的值为1。

 

从上面这个简单的例子,我们初步了解了静态作用域和动态作用域对变量查找的不同处理规则。但在语言的实现中,系统如何去维护静态作用域呢?下面我们简单地了解一下活动记录中的访问链。

活动记录的访问链

在采用静态作用域和块结构的语言中,控制栈的活动记录中通过访问链来维护静态作用域。一个活动记录的访问链(access link)指向源程序中最近的外层块所对应的活动记录。访问链有时也称为静态链(static link)。下面我们就通过上面那个C语言程序片段来简单了解访问链的控制。当执行语句f(3),并在函数f内部调用g(12)后,控制栈中部分的活动记录如图2所示。

 

图 2

这里假设C语言中每个声明视为在不同的块中,即每个声明都需要一个活动记录。图2中左边的箭头表示的是控制链,右边的箭头表示的是访问链。其中每个控制链都是指向前一个活动记录,而访问链而可能跳过若干活动记录,但都是指向控制栈中之前的某个活动记录。另外需要注意一下几点:

  1. 函数g声明位于变量x的声明所在的作用域内部,所以包含函数g声明的活动记录的访问链指向变量x声明的活动记录。同理,包含函数f声明的活动记录的访问链指向包含函数g声明的活动记录。
  2. 执行函数调用f(3)时,压入一个与函数f的函数体相关的活动记录,在创建这个活动记录时,系统会去寻找包含f声明的活动记录(记为f_record),然后将新创建的活动记录的访问链指向活动记录f_record。
  3. 执行函数调用g(12)时,新建一个与函数g的函数体相关的活动记录,然后压入控制栈中。该活动记录的访问链则指向包含函数g声明的活动记录。
  4. 在执行表达式x+z时,在函数调用g(12)的活动记录中没有找到有关变量x的声明,则通过该活动记录的访问链找到包含函数g声明的活动记录,依然没有找到变量x的声明,然后继续沿着访问链向上查找,最终找到最外层作用域中的变量x的声明(即x=1)。

 

上面通过一个简单的例子说明了活动记录的访问链如何维护静态作用域规则。实际上,只有在允许函数定义在另一个函数定义内部或者其他嵌套块内部的语言才需要访问链。C语言的所有函数定义都是在最外层定义域中的,所以不需要访问链(本文C语言示例中对声明的处理做了假设)。

posted on 2012-03-10 12:16 lienhua34 阅读(...) 评论(...) 编辑 收藏

导航