Static (Lexical) Scoping vs Dynamic Scoping (Pseudocode)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | 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。
这些结果就是我们教授给我们的。然而,我一辈子都无法理解他是如何取得这些成果的。是否有人可能遍历伪代码并在两种不同类型的作用域中解释它们的值?
对于静态(词汇)范围,程序源代码的结构决定了您所引用的变量。对于动态范围,程序堆栈的运行时状态决定了您所引用的变量。这可能是一个非常不熟悉的概念,因为现在广泛使用的每种编程语言(除了EmacsLisp)基本上都使用词汇范围,这对于人类和分析工具来说都是非常容易理解的。
考虑这个更简单的示例程序(用您的伪代码语法编写):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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; } |
程序和编译器将这两个变量都称为
通过词法范围界定,我们在编译时根据程序源代码的静态词法结构来确定所指的
通过动态范围界定,我们有一堆在运行时跟踪的变量定义——因此,我们写入的
此外,考虑一下这个稍有不同的程序:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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(); } |
注:
这是你教授的代码,用哪个精确变量来注释,用词法范围来写。在程序结束时打印的写入文件用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | 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 } |
这里是动态范围。注意,只有在
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | 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 2 3 4 5 6 7 8 9 10 | f2(){ f1(){ } f3(){ f1() } } |
静态的:
这基本上是文本的,第一个变量被定义或不被签入本地函数(让我们把它命名为f1()),如果不在本地函数f1()中,那么变量将在包含此函数的函数f2()中被搜索(我的意思是f1()),这将继续……直到找到变量。
动态的:这与静态不同,从某种意义上说,因为它更为运行时或动态,第一个变量被定义或不被签入本地函数,如果不在本地函数f1()中,那么变量将在调用此函数的函数f3()中被搜索(我的意思是f1()),这将继续……直到找到变量。
关键是词汇图如下所示:
1 | B <- A -> C -> D |
虽然调用图如下所示:
1 | A -> C -> D -> B |
唯一的区别是B族的血统。在词汇图中,b直接定义在a(全局范围)的范围内。在动态图片中,b处的堆栈已经在c的顶部有d,然后是a。
这种差异相当于关键词
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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突变
注意,虽然一个函数只定义了一次*,但是从多个地方调用它是很常见的(甚至可以递归地调用它自己)。因此,虽然使用静态代码执行词法范围界定非常简单,但是动态范围界定更复杂,因为在对该函数的不同调用期间(要求您单步执行程序并跟踪调用堆栈ch的方式),同一关键字(在同一函数中)可能解析为不同的变量(来自不同的名称空间)。执行过程中的更改)。
*(模板语言除外….)