使用静态(词法)作用域,程序源代码的结构决定了您所指的变量。使用动态范围,程序堆栈的运行时状态决定了您所指的变量。这可能是一个非常陌生的概念,因为今天广泛使用的每种编程语言(可能除了 emacs lisp 之外)都使用词法范围,这对于人类和分析工具来说往往更容易推理。
考虑这个更简单的示例程序(用您的伪代码语法编写):
program a() {
x: integer; // "x1" in discussions below
x = 1;
procedure b() {
x = 2; // <-- which "x" do we write to?
}
procedure c() {
x: integer; // "x2" in discussions below
b();
}
c();
print x;
}
程序和编译器将这两个变量都称为x,但我将它们标记为x1 和x2 以方便下面的讨论。
通过词法作用域,我们在编译时根据程序源代码的静态词法结构确定我们指的是哪个x。当定义 b 时,x 在范围内的最内层定义是x1,因此有问题的写入解析为x1,这就是x = 2 写入的位置,所以我们打印@ 987654331@ 运行此程序。
使用动态范围,我们有一堆在运行时跟踪的变量定义——因此我们写入的x 取决于范围内的确切内容,并且在运行时动态定义。开始运行a将x => x1推入堆栈,调用c将x => x2推入堆栈,然后当我们到达b时,堆栈的顶部是x => x2,所以我们写进入x2。这使x1 保持不变,因此我们在程序结束时打印1。
此外,考虑这个稍微不同的程序:
program a() {
x: integer; // "x1" in discussions below
x = 1;
procedure b() {
x = 2; // <-- which "x" do we write to?
}
procedure c() {
x: integer; // "x2" in discussions below
b();
}
c();
b();
}
注意b 被调用了两次——第一次通过c,第二次直接调用。使用词法作用域,上面的解释没有改变,我们两次都写入x1。但是,对于动态范围,它取决于 x 在运行时的绑定方式。第一次调用b,我们写入x2,如上所述——但第二次,我们写入x1,因为那是堆栈顶部的内容! (x => x2 在c 返回时弹出。)
所以,这里是你教授的代码,用词法作用域注释了哪个确切的变量用于哪个写入。最终在程序末尾打印的写入标有*:
program A()
{
x, y, z: integer; // x1, y1, z1
procedure B()
{
y: integer; // y2
y=0; // y2 = 0
x=z+1; // x1 = z1 + 1 = 12 + 1 = 13*
z=y+2; // z1 = y2 + 2 = 0 + 2 = 2*
}
procedure C()
{
z: integer; // z2
procedure D()
{
x: integer; // x2
x = z + 1; // x2 = z2 + 1 = 5 + 1 = 6
y = x + 1; // y1 = x2 + 1 = 6 + 1 = 7*
call B();
}
z = 5; // z2 = 5
call D();
}
x = 10; // x1 = 10
y = 11; // y1 = 11
z = 12; // z1 = 12
call C();
print x, y, z; // x1, y1, z1
}
这里是动态范围。请注意,仅更改位于B 和* 标签的位置:
program A()
{
x, y, z: integer; // x1, y1, z1
procedure B()
{
y: integer; // y2
y=0; // y2 = 0
x=z+1; // x2 = z2 + 1 = 5 + 1 = 6
z=y+2; // z2 = y2 + 2 = 0 + 2 = 2
}
procedure C()
{
z: integer; // z2
procedure D()
{
x: integer; // x2
x = z + 1; // x2 = z2 + 1 = 5 + 1 = 6
y = x + 1; // y1 = x2 + 1 = 6 + 1 = 7*
call B();
}
z = 5; // z2 = 5
call D();
}
x = 10; // x1 = 10*
y = 11; // y1 = 11
z = 12; // z1 = 12*
call C();
print x, y, z;
}