【问题标题】:Static (Lexical) Scoping vs Dynamic Scoping (Pseudocode)静态(词法)作用域与动态作用域(伪代码)
【发布时间】:2014-04-19 02:24:22
【问题描述】:
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。

这些结果是我们的教授给我们的。但是,我终生无法理解他是如何达到这些结果的。有人可以遍历伪代码并在两种不同类型的范围内解释它们的值吗?

【问题讨论】:

  • 你的伪代码中那些z: integer;的确切含义是什么?如果他们定义了一个新变量,他们有确定的默认值吗?
  • 相当于int z;
  • @leeduhem 据我所知,在初始化之前没有访问任何变量。
  • i.imgur.com/f6wT7pB.png 不确定这是否有帮助,它已包含在解决方案中。
  • @sepp2k 你说得对,我看错了代码。

标签: scope output pseudocode


【解决方案1】:

使用静态(词法)作用域,程序源代码的结构决定了您所指的变量。使用动态范围,程序堆栈的运行时状态决定了您所指的变量。这可能是一个非常陌生的概念,因为今天广泛使用的每种编程语言(可能除了 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,但我将它们标记为x1x2 以方便下面的讨论。

通过词法作用域,我们在编译时根据程序源代码的静态词法结构确定我们指的是哪个x。当定义 b 时,x 在范围内的最内层定义是x1,因此有问题的写入解析为x1,这就是x = 2 写入的位置,所以我们打印@ 987654331@ 运行此程序。

使用动态范围,我们有一堆在运行时跟踪的变量定义——因此我们写入的x 取决于范围内的确切内容,并且在运行时动态定义。开始运行ax =&gt; x1推​​入堆栈,调用cx =&gt; x2推入堆栈,然后当我们到达b时,堆栈的顶部是x =&gt; 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 =&gt; x2c 返回时弹出。)

所以,这里是你教授的代码,用词法作用域注释了哪个确切的变量用于哪个写入。最终在程序末尾打印的写入标有*

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;
}

【讨论】:

  • 这个答案应该被接受;它很好地解释了词法作用域和动态作用域之间的区别,尤其是添加了更易于阅读的示例程序。
  • 这是一个很好的答案,非常清楚地解释了静态范围和动态范围之间的区别。我也很欣赏使用 x1、x2 来区分不同变量的方式。顺便说一句,当我在谷歌搜索“动态范围”时,它突出显示了这个答案,并将我指向这里。很酷的用户体验!
  • 只是想知道JavaScript的this关键字是否也可以视为动态作用域的示例?因为this 没有绑定在词法范围内。
  • 好吧,我发现了一个错误。在动态范围内,应该是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 }
  • 对静态和动态范围的最佳解释之一。学到了很多。谢谢。
【解决方案2】:

静态范围和动态范围是在以任何语言编写的程序中查找具有特定唯一名称的特定变量的不同方法。

它特别有助于解释器或编译器决定在哪里以及如何找到变量。

考虑代码如下,

f2(){

   f1(){
   }

   f3(){
    f1()
   }

}

静态:

这基本上是文本的,第一个变量是否定义将在本地函数中检查(我们将其命名为 f1()),如果不在本地函数 f1() 中,则将在函数 f2() 中搜索变量封闭的 this 函数(通过 this 我的意思是 f1()),...继续...直到找到变量。

动态:

这与静态不同,在某种意义上,因为它更多是运行时或动态,第一个变量是否定义将在本地函数中检查,如果不在本地函数 f1() 中,则将在中搜索变量调用 this 函数的函数 f3()(通过 this 我的意思是 f1() 再次),...继续...直到找到变量。

【讨论】:

  • 这个真的很简单。
【解决方案3】:

关键是词法图是这样的:

B <- A -> C -> D

而调用图如下所示:

     A -> C -> D -> B

唯一的区别是血统 B 有什么。在词法图中,B 直接定义在 A 的作用域(全局作用域)中。在动态图中,B 处的堆栈已经有 D 在 C 之上,然后是 A。

这种差异相当于关键字 xz 在 B 中的解析方式。在词汇上,它们被标识为 A.xA.z,但动态地,它们被标识为 D.x 和(因为没有D.z 存在)和C.z

def B:
    B.y = 0
    __x = __z + 1
    __z = B.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.xA.z

请注意,虽然一个函数只定义一次*,但通常会从多个位置调用它(它甚至可以递归调用自身)。因此,虽然使用静态代码执行词法作用域相当简单,但动态作用域更复杂,因为相同的关键字(在同一个函数中)可能在对该函数的不同调用期间解析为不同的变量(来自不同的名称空间)(要求您单步执行程序并跟踪调用堆栈在执行过程中的变化)。

*(模板语言除外..)

【讨论】:

    猜你喜欢
    • 2013-11-03
    • 1970-01-01
    • 2016-09-23
    • 1970-01-01
    • 2012-05-02
    • 1970-01-01
    • 2015-12-20
    • 1970-01-01
    相关资源
    最近更新 更多