Ein paar Worte vorabHome   Letzte MeldungenNews   Index der Kapitel und der besprochenen FunktionenIndex   Wer ich bin, warum ich diese Seiten mache, KontaktImpressum   Ich freue mich über jeden Eintrag im Gästebuch!Gästebuch   Einige Links zu anderen AutoLisp-SeitenLinks   Copyrights und DisclaimerRechts
Hier können die kompletten Seiten als ZIP-File heruntergeladen werden!

Funktionen für komfortables Arbeiten mit Zeichenketten String-Tango
Noch mehr Funktionen für komfortableres Arbeiten mit Zeichenketten Kettenhunde
strtok zerlegt Zeichenketten anhand eines Trennzeichens Tock-Tock
Arbeiten mit Datum und Zeit in AutoLisp Zeitlos...
Dotted pairs - wie man den Programmabbruch verhindert Gepunktet?
Neue Funktionen für die Listenbearbeitung Strukturtapete
Weitere neue Funktionen für die Listenbearbeitung Listen to me!
Lambda expressions - dasSalz in der Suppe Lambada
Lambda expressions anhand eines Praxisbeispiels Unter der Erde
Where und whereever erleichtern den Umgang mit Listen Quo vadis?
Rekursion - Funktionen, die sich selbst aufrufen Katzenschwanz
Ein äusserst wichtiger Prototyp für Funktionen Nix passiert
Wo das lineare mapcar am Ende ist Tiefer rein!
Über Effekte und Neben(Seiten-)Effekte von Funktionen Seitensprünge
Die Namensräume (Sichtbarkeit) von Variablen Raumwunder
Let dient zur Schaffung kleinerer Namensräume Lass mal...
Sukzessive Verarbeitung von Listenresten mit mapcdr Der Bruder
Was in AutoLisp einfach nicht machbar ist (Teil I) Beschränkt
Was in AutoLisp einfach nicht machbar ist (Teil II) Limited Edition
Nicht mit Effekten arbeiten, sondern direkter Daten-Änderung Destruktiv
Sequenzielles vs. paralleles Abarbeiten von Argumenten Parallelwelten
Über den Umgang mit Funktionsschablonen Erwachet!
Ein Praxiskapitel über Auswahlsätze, Attribute, wcmatch und mehr Durch die Brust
Die Farben des AutoCAD Color Index und ihre RGB-Werte Alles so schön
Hier laufen die Fäden zusammen: Viele Konzepte vereint Lapsus Lispuli
Ein Spiel als Beispiel für lernfähige Funktionen Zug um Zug
Errorhandling in AutoLisp - Teil 1 Alles valsch!
Errorhandling in AutoLisp - Teil 2 Und foll Feler!


Zum Einsteiger-Tutorial

Zu den ActiveX-Seiten

Meine Private HP mit Fotos, Gedichten, Musik und Postkartenversand

Mein Online-Lexikon der Fotografie

Mein völlig abgedrehtes Reisebüro










In den Lisp-Handbüchern von AutoCAD steht, dass Variablen, die hinter dem Schrägstrich in der Argumentenliste von (defun ...) deklariert werden, lokal sind. Genau genommen stimmt das nicht ganz: Der Namensraum lokaler Variablen ist eigentlich (d.h. in anderen Programmiersprachen) exakt auf die Funktion begrenzt, in der sie deklariert werden.

In AutoLisp sind Variablen, die in einer Funktion deklariert werden, nicht nur innerhalb dieser sichtbar, sondern auch innerhalb aller aus dieser Funktion heraus aufgerufenen weiteren Funktionen! Dies gilt natürlich nur, solange in den Unterfunktionen nicht weitere namensgleiche Variablen existieren, die die weiter oben in der Hierarchie angesiedelten Variablen überdecken.

Um diese Zusammenhänge zu verdeutlichen, hier ein paar (inhaltsleere) Beispiel-Funktionen mit verschiedenen Variablen:
(defun funktion1( / a b c)
  (setq a 11 b 12 c 13)
  (funktion2)
  (list a b c)
)

(defun funktion2( / b c)
  (setq a 21 b 22 c 23)
  (funktion 3)
  (list a b c)
)

(defun funktion3( / c)
  (setq a 31 b 32 c 33)
  (list a b c)
)
                  
Der Namensraum von a erstreckt sich eindeutig über funktion1 einschliesslich der Aufrufe von funktion2 und funktion3, d.h., die Variable ist in allen drei Funktionen sichtbar und ihr Wert kann überall verändert werden! Unter dem Namen b existieren aber schon zwei Variablen: Die eine wird in funktion1 deklariert, ist aber in funktion2 nicht mehr sichtbar, da sie dort von einer anderen, jedoch namensgleichen Variablen verdeckt wird.

Die Variable c schliesslich ist dreifach vorhanden, in jeder der drei Funktionen existiert eine! Betrachten wir das Ganze nach Funktionen: In funktion1 sind b und c also lokale Variablen, a ist nicht wirklich lokal, da der Namensraum größer ist. In funktion2 ist c lokal, aber der Namensraum von b deckt auch funktion3 mit ab. Was wird also die Rückgabe von funktion1 sein? Richtig: (31 12 13)!

Betrachten wir nun folgende Funktion:
(defun foo (a b / c d e)
  (setq c (* a b)
        d (/ a b)
        e (+ a b)
        f (- a b)
  )
  (list c d e f)
)
                  
Richtig, es fällt auf, dass f hier nicht als 'lokale' Variable deklariert wurde! Aber es ist ein Fehler, nun den Schluss daraus zu ziehen, f sein eine globale Variable! Alles, was wir aufgrund dieser Funktion sagen können, ist, dass f nicht zu dieser Funktion lokal ist.

Schlimmer noch! Wir wissen ja bisher gar nicht, wer diese Funktion aufruft! Nehmen wir an, wir suchen ein wenig im Code und finden diese beiden hier:
(defun bar( / f)
  (foo 33 44 55 66)
  ...
)

(defun baz( / )
  (foo 33 44 55 66)
  ...
)
                  
Wird foo von bar aufgerufen, dann ändert die aufgerufene Funktion den Wert von f in bar, da die Variable ja auch in bar sichtbar ist! baz hingegen würde durch (setq f ...) stillschweigend eine globale Variable erzeugen ... oder auch nicht! Schliesslich kann es ja sein, dass um unsere Anordnung von bar, baz und foo herum noch weitere Funktionen liegen, in denen eine Variable f deklariert ist. Diese würde dann (evtl. unabsichtlich und unbemerkt) verändert, aber es enstünde keine globale Variable!

Es wird jetzt klar, dass hier ein ungeheures Fehlerpotenzial liegt! Wir können ja nicht einmal in einem Programm nachverfolgen, von welchen anderen Funktionen eine ganz bestimmte Funktion aufgerufen wird. Natürlich kann man den Quellcode eines Programms mit dem Texteditor danach durchsuchen, aber würde man dann z.B. auch diese Funktion finden ??? :
(defun heimlich( / q r)
  (setq q'(0 7))
  (setq r(str-list "fehlerpotenzial"))
  (apply
    (read
      (apply'strcat
        (mapcar
        '(lambda(w / )(nth w r))
          (append q(list(last q)))
        )
      )
    )
    q
  )
)
                  
Jawohl, auch diese Funktion ruft (ganz heimlich) foo auf, wenn es auch nicht auf Anhieb sichtbar ist! Kein Texteditor wird jemals in der Lage sein herauszukriegen, dass hier foo aufgerufen wird! Folgende Fehler sind in unserem Szenario vorstellbar:
  • In einer Funktion wird vergessen, eine lokale Variable in die Argumentenliste einzutragen. Im günstigeren Falle entsteht eine globale Variable, im ungüstigeren Fall manipulieren wir eine namensgleiche Variable in einer übergeordneten Funktion.
  • Ein Programm verlässt sich auf die Manipulation einer weiter oben deklarierten Variablen in tifer angesiedelten Unterfunktionen. Bei einer Änderung des Programms wird irgendwo in der Hierarchie der Funktionsaufrufe eine neue Variable eingeführt, die zufällig den gleichen Namen hat. Plötzlich funktioniert nichts mehr!
  • Ein Programm arbeitet mit globalen Variablen... Der Rest verhält sich genauso wie im vorigen Beispiel.
Hier noch ein Code-Beispiel, wie durch einen kleinen Flüchtigkeitsfehler eine ganz ordentliche Schleife zur Endlosschleife mutieren kann:
(defun f1( / i)
  (setq i 0)
  (while(< i 10)
    (f2)
  )
)

(defun f2( / j)
  (setq i 0)
  (while(< i 5)
    ...
  )
)
                  
Dadurch, dass hier beim Eintrag in die Argumentenliste statt einem i ein j eingetippt wurde, greift f2 nun auf die Variable i aus f1 zu. Diese wird immer wieder auf 0 gesetzt, sodass die Schleife in f1 weiterläuft, bis sie vom Benutzer endlich abgebrochen wird.

Was kann man denn nun tun, um sich vor solchen Fehlern zu schützen? Nun, es ist nicht leicht, und einen vollständigen Schutz gibt es ganz sich nicht. Aber man kann sich an ein paar Regeln halten:
  • Die allererste und wichtigste Massnahme ist natürlich, Variablen immer schön lokal zu deklarieren und mehr als einmal nachzusehen, ob man auch keine vergessen hat.
  • Gewollte und notwendige globale Variablen als solche kennzeichnen! In Lisp hat sich eingebürgert, dass der Name einer globalen Variablen mit Sternchen eingefasst wird: *my_global* oder *config*. Auf unnötige globale Variablen sollte man verzichten!
  • Man sollte möglichst wenig Gebrauch von der Tatsache machen, dass Variablen auch in Unterfunktionen sichtbar sind. Allerdings ist man manchmal auf diesen Mechanismus angewiesen, z.B. bei Rekursionen und auch bei wechselrekursiven Funktionen, die gemeinsam auf eine Variable zugreifen. In diesen Fällen kann man das Problem meist umgehen, indem man auch die entsprechenden Funktionen lokal macht (verschachtelte Funktionsdefinitionen).
  • Möchte man wirklich mit einem Namensraum über mehrere Funktionsebenen arbeiten, dann empfiehlt es sich, die Variablen entsprechend zu bezeichnen (ich halte es so, dass der Variablenname den Namen der Funktion enthält, in der die Variable definiert wurde) und in den Kommentaren der Unterfunktion zu erwähnen, dass hier auf Variablen aus übergeordneten Funktionen zugegriffen wird.