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です。
ただし、dynamic scopingを使用して実行すると、結果はx = 10、y = 7、およびz = 12になります。
これらの結果は、教授が私たちに与えたものです。しかし、彼がこれらの結果にどのように到達したか、私は一生理解できません。誰かがおそらく擬似コードを調べて、2つの異なるタイプのスコープで値を説明できますか?
static(lexical)scopingを使用すると、プログラムのソースコードの構造によって、参照する変数が決まります。 動的スコープを使用すると、プログラムスタックのランタイム状態によって、参照している変数が決まります。基本的に今日広く使用されているすべてのプログラミング言語(おそらく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
を参照しているかを判断します。 definingx
がx1
である場合、スコープ内のb
の最も内側の定義であるため、問題の書き込みはx1
に解決され、それはx = 2
は書き込みますので、このプログラムの実行時に2
を出力します。
動的スコープでは、実行時に変数定義のスタックが追跡されます。したがって、どのx
に書き込むかは、スコープ内にあるものに依存し、runtimeで動的に定義されます。 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
は2回呼び出されます-最初はc
を介して、2回目は直接呼び出されます。字句スコープでは、上記の説明は変更されず、両方ともx1
に書き込みます。ただし、動的スコープでは、x
が実行時にバインドされる方法に依存します。最初にb
を呼び出すと、上で説明したようにx2
に書き込みますが、2回目はx1
に書き込みます。これがスタックの一番上にあるからです! (c
が戻ると、x => x2
がポップされます。)
それで、ここにあなたの教授のコードがあり、どの変数がレキシカルスコーピングで書かれているかについての注釈が付けられています。プログラムの最後に印刷される書き込みには、*
のマークが付けられます。
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;
}
静的スコープと動的スコープは、任意の言語で書かれたプログラムで特定の一意の名前を持つ特定の変数を見つけるためのさまざまな方法です。
インタープリターまたはコンパイラーが変数を見つける場所と方法を決定するのに特に役立ちます。
以下のようなコードを考えてください、
f2(){
f1(){
}
f3(){
f1()
}
}
これは基本的にテキストであり、最初の変数はローカル関数で定義されているか、チェックされていません(f1()と名付けます)、ローカル関数f1()ではない場合、変数は囲まれた関数f2()で検索されます- this function(by thisつまりf1()を意味する)、...これは続きます...変数が見つかるまで。
これは、よりランタイムまたは動的であるという意味で静的とは異なり、最初の変数がローカル関数で定義されているかどうかがチェックされます。ローカル関数f1()でない場合、変数は関数f3( )this function(by thisもう一度f1()を意味する)を呼び出し、...これは続きます...変数が見つかるまで。
重要なのは、字句グラフが次のように見えることです。
B <- A -> C -> D
一方、コールグラフは次のようになります。
A -> C -> D -> B
唯一の違いは、系統Bが持っているものです。字句図では、BはAのスコープ(グローバルスコープ)で直接定義されています。動的な図では、BのスタックはすでにCの上にDを持ち、次にAを持ちます。
この違いは、キーワードx
とz
がBでどのように解決されるかということです。字句的には、それらは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
を変更します。
関数は一度しか定義されない*ことに注意してください。複数の場所から呼び出すのが一般的です(再帰的に呼び出すこともあります)。したがって、静的コードを使用して字句スコープを実行することはかなり簡単ですが、動的スコープは、同じキーワード(同じ関数内)がその関数の異なる呼び出し中に(異なる名前空間から)異なる変数に解決する可能性があるため、より複雑です(プログラムをステップ実行し、実行中にコールスタックがどのように変化するかを追跡する必要があります)。
*(テンプレート言語を除く..)