Program A()
{
x, y, z: integer;
procedure B()
{
y: integer;
y=0;
x=z+1;
z=y+2;
}
procedure C()
{
z: integer;
procedure D()
{
x: integer;
x = z + 1;
y = x + 1;
call B();
}
z = 5;
call D();
}
x = 10;
y = 11;
z = 12;
call C();
print x, y, z;
}
根据我的理解,使用静态范围运行时此程序的结果是:x = 13,y = 7,z = 2.
但是,当使用动态范围运行时,结果为:x = 10,y = 7,z = 12.
这些结果是我们教授给我们的结果。但是,我无法理解他的生活如何达到这些结果。有人可能会通过伪代码并在两种不同类型的范围内解释它们的值吗?
答案 0 :(得分:178)
使用静态(词法)范围,程序源代码的结构决定了您所引用的变量。使用动态范围,程序堆栈的运行时状态决定了您所引用的变量。这可能是一个非常陌生的概念,因为基本上今天广泛使用的每种编程语言(除了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
。 定义 x
时范围内b
的最内层定义是x1
,因此有问题的写入会解析为x1
,而且#39} ; x = 2
写入的位置,因此我们在运行此程序时打印2
。
使用动态范围,我们在运行时跟踪了一堆变量定义 - 因此我们写入的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
,因为那是什么&#39}堆栈顶部! (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;
}
答案 1 :(得分:12)
静态作用域和动态作用域是在用任何语言编写的程序中查找具有特定唯一名称的特定变量的不同方法。
它特别有助于解释器或编译器决定在何处以及如何查找变量。
考虑代码如下,
f2(){
f1(){
}
f3(){
f1()
}
}
这基本上是文本的,第一个变量是否定义将在本地函数中检查(让它命名为f1()),如果不在本地函数f1()中,那么变量将在函数f2()中搜索到附上这个函数(通过这个我的意思是f1()),...这继续...直到找到变量。
这与静态不同,在某种意义上,因为它更多是运行时或动态的,所以定义或不定义的第一个变量将在本地函数中检查,如果不在本地函数f1()中,那么将在函数f3()调用这个函数(通过这个我的意思是f1(再次)),...这继续...直到找到变量。
答案 2 :(得分:1)
关键是词汇图看起来像这样:
B <- A -> C -> D
而调用图如下所示:
A -> C -> D -> B
唯一的区别是血统B的含义。在词汇图中,B直接在A(全局范围)的范围内定义。在动态画面中,B处的堆栈已经在C的顶部有D,然后是A.
这种差异与B中解决关键字x
和z
的方式有关。词汇表中,它们标识为A.x
和A.z
,但动态识别它们使用D.x
和(因为不存在D.z
)C.z
。
def B:
B.y = 0
x = z + 1
z = y + 2
def C:
def D:
D.x = z + 1
y = D.x + 1
call B
C.z = 5
call D
A.x, A.y, A.z = 10, 11, 12
call C
print A.x, A.y, A.z
上面我尝试更清楚地表示您的代码。请注意,D根据名称解析的两种方法改变A.y
,而如果选择词汇而不是动态范围,B只会改变A.x
和A.z
。
请注意,虽然函数只被定义一次*,但通常从多个地方调用它(它甚至可以递归地调用它自己)。因此,虽然使用静态代码执行词法作用域是相当简单的,但动态作用域更复杂,因为在对该函数的不同调用期间,相同的关键字(在同一函数中)可以解析为不同的变量(来自不同的名称空间)(要求您逐步执行程序并跟踪调用堆栈在执行期间的变化情况。
*(模板语言除外..)