本文为《编译原理》(龙书) 的读书笔记 (1.6)
静态和动态的区别
在为一个语言设计一个编译器时,我们所面对的最重要的问题之一是编译器能够对一个程序做出哪些判定
- 如果一个语言使用的策略支持编译器静态决定某个问题,那么我们说这个语言使用了一个静态(static)策略,或者说这个问题可以在编译时刻(compile time)决定
- 一个只允许在运行程序的对候做出决定的策略被称为动态策略(dynamic policy) , 或者被认为需要在运行时刻(runtime)做出决定
我们需要注意的另一个问题是声明的作用域
- 如果仅通过阅读程序就可以确定一个声明的作用域, 那么这个语言使用的是静态作用域(static scope) , 或者说词法作用域(lexical scope)
- 否则,这个语言使用的是动态作用域(dynamic scope)。如果使用动态作用域,当程序运行时,同一个对 x x x 的使用会指向 x x x 的几个声明中的某一个
大部分语言( 比如 C 和 Java ) 使用静态作用域
名字和标识符
标识符(identifier)是一个字符串,通常由字母和数字组成。它用来指向一个实体
所有的标识符都是名字,但并不是所有的名字都是标识符。名字也可以是一个表达式。比如名字x.y
可以表示x
所指的一个结构中的字段y
。这里x
和y
是标识符。像x.y
这样的复合名字称为受限名字(qualified name)
动态作用域
从技术上讲,如果一个作用域策略依赖于一个或多个只有在程序执行时刻才能知道的因素,它就是动态的。然而,术语动态作用域通常指的是下面的策略:对一个名字 x x x 的使用指向的是最近被调用但还没有终止且声明了 x x x 的过程中的这个声明
这种类型的动态作用域仅仅在一些特殊情况下才会出现。我们将考虑两个动态作用域的例子:
- C预处理器中的宏扩展
- 面向对象编程中的方法解析
例1.7
在图1-12给出的C程序中,标识符a
是一个代表了表达式(x+ 1)
的宏。但
x
x
x 到底是什么呢?我们不能够静态地解析
x
x
x
实际上,为了解析 x x x , 我们必须使用前面提到的普通的动态作用域规则。我们检查所有当前活跃的函数调用,然后选择最近调用的且具有一个对 x x x 的声明的函数。对 x x x 的使用就是指向这个声明
在图1-12的例子中,函数main
首先调用函数b
。b
有一个
x
x
x 的声明,因此b
中的printf
中的(x + 1)
指向这个
x
x
x 。因此,打印出的值是2
在b
运行结束之后,函数c
被调用,唯一可以被c
访问的x
是全局变量x
。函数c
中的printf
语句打印的值是3
动态作用域解析对多态过程是必不可少的。所谓多态过程是指对于同一个名字根据参数类型具有两个或多个定义的过程
别名
引用调用或者其他类似的方法,会引起一个有趣的结果。有可能两个形式参数指向同一个位置,这样的变量称为另一个变量的别名(ailas)。结果是,任意两个看起来从两个不同的形式参数中获得值的变量也可能变成对方的别名
事实上,如果编译器要优化一个程序,就要理解别名现象以及产生这一现象的机制。在很多情况下我们必须在确认某些变量相互之间不是别名之后才可以优化程序
比如,我们可能确定x=2
是变量x
唯一被赋值的地方。如果是这样,那么我们可以把对x
的使用替换为对2
的使用。比如,把a = x +3
替换为较简单的a = 5
。但是,假设有另一个变量y
是x
的别名。那么,一个赋值语句y = 4
可能具有意想不到的改变x
的值的效果。这可能也意味着把a = x +3
替换为a = 5
是一个错误