web-dev-qa-db-fra.com

Portée statique (lexicale) vs portée dynamique (pseudocode)

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

D'après ma compréhension, le résultat de ce programme lorsqu'il est exécuté avec portée statique est: x = 13, y = 7 et z = 2.

Cependant, lorsqu'il est exécuté à l'aide de étendue dynamique, le résultat est: x = 10, y = 7 et z = 12.

Ces résultats sont ceux que notre professeur nous a donnés. Cependant, je ne peux pas comprendre pour la vie de moi comment il a atteint ces résultats. Quelqu'un pourrait-il éventuellement parcourir le pseudocode et expliquer ses valeurs dans les deux types différents de portées?

57
petrov

Avec portée statique (lexicale), la structure du code source du programme détermine les variables auxquelles vous faites référence. Avec portée dynamique, l'état d'exécution de la pile de programmes détermine à quelle variable vous faites référence. C'est probablement un concept très peu familier, car pratiquement tous les langages de programmation largement utilisés aujourd'hui (à l'exception peut-être d'emacs LISP) utilisent la portée lexicale, ce qui tend à être considérablement plus facile à la fois pour les humains et les outils d'analyse.

Considérez cet exemple de programme beaucoup plus simple (écrit dans votre syntaxe de pseudocode):

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

Le programme et le compilateur font référence aux deux variables comme x, mais je les ai étiquetées x1 et x2 pour faciliter la discussion ci-dessous.

Avec la portée lexicale, nous déterminons au moment de la compilation à laquelle x nous nous référons en fonction de la structure statique et lexicale du code source du programme. La définition la plus profonde de x dans la portée lorsque définitionb est x1, et donc l'écriture en question se résout en x1, et c'est là que x = 2 écrit, donc nous imprimons 2 lors de l'exécution de ce programme.

Avec la portée dynamique, nous avons une pile de définitions de variables suivies au moment de l'exécution - de sorte que x que nous écrivons dépend de ce qui est exactement dans la portée et a été défini dynamiquement à runtime. Commencer à exécuter a pousse x => x1 sur la pile, en appelant c pousse x => x2 sur la pile, puis lorsque nous arrivons à b, le haut de la pile est x => x2, et donc nous écrivons dans x2. Cela laisse x1 intact, et donc nous imprimons 1 à la fin du programme.

En outre, considérez ce programme légèrement différent:

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

Remarque b est appelé deux fois - la première fois via c, la deuxième fois directement. Avec la portée lexicale, l'explication ci-dessus n'est pas modifiée et nous écrivons dans x1 les deux fois. Cependant, avec la portée dynamique, cela dépend de la façon dont x est lié au moment de l'exécution. La première fois que nous appelons b, nous écrivons dans x2 comme expliqué ci-dessus - mais la deuxième fois, nous écrivons dans x1, puisque c'est ce qui se trouve au sommet de la pile! (x => x2 apparaît lorsque c revient.)

Donc, voici le code de votre professeur, annoté avec quelle variable exacte est utilisée sur quelle écriture avec une portée lexicale. Les écritures qui finissent par être imprimées à la fin du programme sont marquées d'un *:

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
}

Et le voici avec une portée dynamique. Notez que les modifications niquement se trouvent dans B et à l'emplacement de * Mots clés:

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;
}
183
Josh Watzman

La portée statique et la portée dynamique sont différentes façons de trouver une variable particulière avec un nom unique spécifique dans un programme écrit dans n'importe quelle langue.

Il est particulièrement utile pour l'interpréteur ou le compilateur de décider où et comment trouver la variable.

Considérez que le code est comme ci-dessous,

f2(){

   f1(){
   }

   f3(){
    f1()
   }

}

Statique:

Ceci est essentiellement textuel, la première variable est définie ou non sera vérifiée dans la fonction locale (nommons-la f1 ()), sinon dans la fonction locale f1 (), puis la variable sera recherchée dans la fonction f2 () qui contenait - this fonction (par this je veux dire f1 ()), ... cela continue ... jusqu'à ce que la variable soit trouvée.

Dynamique:

C'est différent de statique, dans le sens où c'est plus runtime ou dynamique, la première variable est définie ou non sera vérifiée dans la fonction locale, sinon dans la fonction locale f1 (), puis la variable sera recherchée dans la fonction f3 ( ) qui appelait la fonction this (par this je veux dire encore f1 ()), ... cela continue ... jusqu'à ce que la variable soit trouvée.

12
Prakhyat

Le nœud est que le graphe lexical ressemble à ceci:

B <- A -> C -> D

tandis que le graphe d'appel ressemble à ceci:

     A -> C -> D -> B

La seule différence est la lignée B. Dans l'image lexicale, B est défini directement dans la portée de A (la portée globale). Dans l'image dynamique, la pile en B a déjà D en haut de C puis A.

Cette différence revient à la façon dont les mots clés x et z sont résolus dans B. Lexicalement, ils sont identifiés avec A.x et A.z, mais dynamiquement, ils sont identifiés par D.x et (puisqu'aucun D.z existe) avec 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

Ci-dessus, j'ai essayé de représenter votre code plus clairement. Notez que D mute A.y selon les deux méthodes de résolution de nom, alors que B ne mute que A.x et A.z si la portée lexicale plutôt que dynamique est choisie.

Notez que si une fonction n'est définie qu'une seule fois *, il est courant de l'appeler à partir de plusieurs endroits (et elle peut même s'appeler récursivement). Ainsi, bien qu'il soit assez trivial d'effectuer une portée lexicale à l'aide du code statique, la portée dynamique est plus compliquée car le même mot clé (dans la même fonction) peut se résoudre en différentes variables (à partir d'espaces de noms différents) lors d'appels différents à cette fonction ( vous obligeant à parcourir le programme et à suivre l'évolution de la pile d'appels pendant l'exécution).

* (Sauf dans les langages de modèles ..)

1
benjimin