- Speicherinterferenz
-
In der Programmierung ist eine Variable im allgemeinsten Sinne einfach ein Behälter für Rechnungsgrößen („Werte“), die im Verlauf eines Rechenprozesses auftreten. Im Normalfall wird eine Variable durch einen Namen bezeichnet und hat eine bestimmte Adresse im Speicher des Rechners. Manchmal ist es sinnvoll, drei Arten von Variablen zu unterscheiden:
- Eingabevariablen erhalten Werte, die von außen ins Programm eingegeben werden,
- Ausgabevariablen enthalten später die Resultate der Rechnung und
- Hilfsvariablen nehmen Werte auf, die im Verlauf der Rechnung benötigt werden.
Diese Unterscheidung ist jedoch rein konzeptuell, die modernen Programmiersprachen nehmen darauf keine Rücksicht.
Inhaltsverzeichnis
Sichtweisen von Variablen
Das Konzept der Variablen wird von Programmiersprachen unterschiedlich interpretiert:
- in einer rein funktionalen Programmiersprache sind Variablen einfach nur Bezeichner, das heißt sie werden wie im Lambda-Kalkül nur dazu verwendet, die Eingabeparameter für eine Funktion zu bezeichnen. Während der Berechnung eines Funktionswerts ändert sich der Wert der Variablen deshalb nicht. Ein Ausdruck mit Variablen hat stets den gleichen Wert, unabhängig davon, an welcher Stelle im Programm er auftritt. Diese Eigenschaft ist unter dem Begriff referentielle Transparenz bekannt.
- in einer imperativen Programmiersprache ist es demgegenüber üblich, dass sich der Wert einer Variablen während der Berechnung laufend ändert. Der gleiche Ausdruck mit Variablen kann deshalb an verschiedenen Stellen des Programms oder zu verschiedenen Zeiten in der Programmausführung ganz unterschiedliche Werte haben.
Dementsprechend definieren verschiedene Programmiersprachen den Begriff der Variablen ganz unterschiedlich. Im Fall von Java heißt es:
Eine Variable ist ein Speicherplatz[1]
In der Sprachdefinition von Scheme heißt es dagegen
Scheme erlaubt es, dass Bezeichner für Speicherplätze stehen, die Werte enthalten. Solche Bezeichner heißen Variablen[2]
Allgemein müssen für eine Variable in einer imperativen Programmiersprache vier Aspekte unterschieden werden:
- der Speicherplatz selbst als Behältnis für Daten,
- die in dem Speicherplatz abgelegten Daten,
- die Adresse des Speicherplatzes und
- der Bezeichner (Name), unter dem der Speicherplatz angesprochen werden kann.
Kompliziert wird die Situation außerdem dadurch, dass unter einem bestimmten Bezeichner an verschiedenen Stellen des Programms oder zu verschiedenen Zeiten in der Programmausführung unterschiedliche Speicherplätze angesprochen sein können und dass es auch anonyme, also namenlose Variablen gibt.
L-Wert und R-Wert von Variablen
Typisch für imperative Programmiersprachen ist, dass ein Bezeichner auf der linken Seite einer Wertzuweisung eine andere Bedeutung („L-Wert“) hat als auf ihrer rechten Seite („R-Wert)“. Die Anweisung
x := x + 1
bedeutet: „Nimm den Wert der Variablen
x
, erhöhe ihn um eins und speichere dies an die Adresse vonx
.“ Der L-Wert einer Variablen ist also ihre Adresse, der R-Wert ihr Inhalt.Variablen als Parameter von Funktionen
Auch die Parameter einer Funktion werden in deren Deklaration durch Variablen repräsentiert, die dann formale Parameter heißen. Beim Aufruf der Funktion werden den formalen Parametern dann (komplexe) Ausdrücke als tatsächlicher Parameter zugeordnet. Für die Übergabe der tatsächliche Parameter an die Funktion gibt es unterschiedliche Mechanismen; verbreitet sind die Übergabe durch Wert und die Übergabe durch Referenz.
Typen von Variablen
Mit jeder Variablen in einem Programm ist notwendigerweise ein bestimmter Datentyp verbunden. Dies ist schon allein deshalb nötig, weil nur der Datentyp festlegt, welche Operationen auf und mit der Variablen sinnvoll und zulässig sind. In der Regel hat der Programmierer die Möglichkeit, in einer Deklaration diesen Typ festzulegen; in vielen Programmiersprachen ist eine solche explizite Deklaration sogar verpflichtend. Andere Programmiersprachen verfügen über implizite Deklarationen, die dann greifen, wenn keine expliziten Deklarationen vorhanden sind. So kannte Fortran die Konvention, dass Variablen, deren Namen mit Buchstaben zwischen
I
undN
beginnen, vom TypINTEGER
sind und alle anderen vom TypREAL
, sofern nichts anderes festgelegt wird. Andere Programmiersprachen kennen sogenannte latente Typen; hier sind Deklarationen nicht nötig, sondern die Maschine erkennt den Typ einer Variablen bei ihrer ersten Verwendung und führt diesen dann stillschweigend weiter mit.Variablen in einer Blockstruktur
Ein wichtiges Konzept von Programmiersprachen ist das Unterprogramm, ob es nun Prozedur, Funktion, Methode oder noch anders heißt. Die allgemeinste Form dieses Konzepts ist der in der Programmiersprache ALGOL 60 erstmals eingeführte Block. Praktisch alle Programmiersprachen, die dieses Konzept in irgendeiner Form anbieten, erlauben es, dass Blöcke ihre eigenen Variablen besitzen, die sich von den Variablen anderer Blöcke eindeutig unterscheiden lassen. Solche Variablen heißen lokale Variablen. Eine Variable, die im ganzen Programm für alle Blöcke zur Verfügung steht, heißt globale Variable. Die Programmiersprache PHP kennt sogar den Begriff einer superglobalen Variablen, die für alle Programme verfügbar sind, die zur gleichen Zeit von einem PHP-Interpreter bearbeitet werden.
Globale Variablen sind scheinbar bequem, weil sie im ganzen Programm sichtbar sind. Es ist nicht notwendig, sie beim Aufruf einer Funktion als Parameter zu übergeben. Sie werden aber auch leicht zur Fehlerquelle, wenn man zum Beispiel eine globale Variable versehentlich oder sogar bewusst für verschiedene Zwecke benutzt.
Auch kann es passieren, dass man eine lokale Variable mit dem Namen der globalen Variablen verwendet, von dem man annimmt, dass er im Programm bisher noch nicht benutzt wurde. Wenn es diesen Namen aber schon als Variable mit passendem Typ gibt (sofern dieser überhaupt vom Compiler und / oder vom Laufzeitsystem geprüft wird), dann wird deren Wert unkontrolliert überschrieben und umgekehrt. Ein schwer zu findender Fehler ist oft die Folge.
Erfahrene Entwickler verwenden globale Variablen nur auf modularer Ebene und ausschließlich dann, wenn es sich nur durch unangemessen hohen Aufwand vermeiden ließe, was auf modernen, leistungsfähigen Rechenmaschinen jedoch im allgemeinen nicht mehr so häufig problematisch ist. Nur bei extrem zeitkritischen Programmen, wie zum Beispiel Echtzeitsystemen kann dies unter Umständen eine Rolle spielen.
Sichtbarkeitsbereich von Variablen (Scope)
Unter dem Sichtbarkeitsbereich einer Variablen versteht man den Programmabschnitt, in dem die Variable nutzbar und sichtbar ist. Da eine lokale Variable in den meisten Programmiersprachen den gleichen Namen tragen darf wie eine globale Variable, sind Sichtbarkeitsbereiche nicht notwendig zusammenhängend: durch die Deklaration einer lokalen Variable wird die gleichnamige globale Variable für einen bestimmten Block „verschattet“, das heißt, sie ist temporär nicht sichtbar.
Die Sichtbarkeitsregeln können auf zwei unterschiedliche (sich gegenseitig ausschliessende) Arten festgelegt werden; dabei ist das Konzept der Bindung wichtig. Bindung bedeutet hier die Zuordnung eines bestimmten Namens zu der damit verbundenen Variablen.
- Lexikalisch (oder statisch), das heißt, der umgebende Programmtext (die Definition) bestimmt die Bindung
- Dynamisch, das heißt, die Ausführungsgeschichte zur Laufzeit des Programms bestimmt die Bindung.
Im Fall einer lexikalischen Bindung sind die Bindungen für einen ganzen Block immer die gleichen, weil sie allein durch die Blockstruktur vorgegeben sind. Man kann daher allein durch Analyse des Programmtextes einer Funktion verstehen, wie sich die Funktion verhält. Dieses Konzept unterstützt daher modulares Programmieren.
Bei dynamischen Bindungsregeln können zur Laufzeit immer neue Bindungen eingeführt werden; diese gelten dann so lange, bis sie explizit aufgehoben werden oder durch eine neue dynamische Bindung verschattet werden. Man kann daher durch Analyse des Programmtextes einer Funktion nicht notwendigerweise verstehen, wie sich diese verhält. Selbst die Analyse des gesamten Programmtextes eines Programms hilft nicht. Es lässt sich statisch nicht verstehen, wie sich die Funktion verhält. Ihr Verhalten hängt von den jeweiligen (direkt und indirekt) aufrufenden Funktionen ab. Dies widerspricht dem Konzept des modularen Programmierens.
Die meisten modernen Programmiersprachen unterstützen nur lexikalische Bindung, z.B. C/C++, ML, Haskell, Python, Pascal. Einige wenige unterstützen nur dynamische Bindung (z.B. Emacs Lisp, Logo) und einige (z.B. Perl und Common Lisp) erlauben dem Programmierer für jede lokale Variable festzulegen, ob sie nach lexikalischen (statischen) oder dynamischen Regeln gebunden sein soll.
Beispiel lexikalisch versus dynamisch
C/C++ verwendet lexikalische (statische) Gültigkeitsbereiche:
int x = 0; int f() { return x; } int g() { int x = 1; return f(); }
Im obigen Programmfragment gibt
g()
immer 0 zurück (den Wert der globalen Variablenx
, nicht den Wert der lokalen Variablenx
ing()
). Dies ist so, weil inf()
immer nur das globalex
sichtbar ist. Würden dagegen dynamische Bindungsregeln verwendet, würdeg()
immer 1 zurückgeben.In Perl können lokale Variablen lexikalisch (statisch) mit dem Schlüsselwort „
my
“ oder dynamisch mit dem irreführenden Schlüsselwort „local
“ deklariert werden[3]. Das folgende Beispiel macht den Unterschied noch einmal klar:$x = 0; sub f { return $x; } sub g { my $x = 1; return f(); } print g()."\n";
Dieses Beispiel benutzt „
my
“ um$x
ing()
einen lexikalischen (statischen) Sichtbarkeitsbereich zu geben. Daher wirdg()
immer 0 zurückgeben.f()
kanng()
's$x
nicht sehen, obwohl dies zum Zeitpunkt, wof()
aufgerufen wird, noch existiert.$x = 0; sub f { return $x; } sub g { local $x = 1; return f(); } print g()."\n";
Dieses Beispiel benutzt „
local
“ um$x
ing()
einen dynamischen Sichtbarkeitsbereich zu geben. Daher wirdg()
immer 1 zurückgeben, daf()
vong()
aufgerufen worden ist.In der Praxis sollte man für lokale Variablen in Perl immer "
my
" verwenden.Lebensdauer von Variablen
Unter der Lebensdauer einer Variablen versteht man den Zeitraum, in dem die Variable Speicherplatz reserviert hat. Wird der Speicherplatz für andere Zwecke wieder freigegeben, so „stirbt“ die Variable und ist nicht mehr nutzbar. Lokale Variablen werden bei jedem Aufruf der Funktion erstellt; in der Regel wird der Speicherplatz beim Verlassen der Funktion wieder freigegeben. Sichtbarkeitsbereich und Lebensdauer von Variablen sind in klassischen blockstrukturierten Programmiersprachen so aufeinander abgestimmt, dass Speicherplatz für eine Variable nur so lange zugeordnet sein muss, wie Code aus dem Sichtbarkeitsbereich ausgeführt wird. Als Konsequenz davon lässt sich eine besonders einfache Art der Speicherverwaltung verwenden: Lokale Variablen werden auf einem Stapelspeicher angelegt, sobald ein Block betreten wird. Technisch gesprochen, wird ein Aktivierungsblock auf einem Laufzeitstapel angelegt, der beim Verlassen des Blocks wieder entfernt wird.
In manchen Programmiersprachen (zum Beispiel C) gibt es einen Zusatz
static
bei der Deklaration, der nur die Sichtbarkeit einer Variablen auf den Namensraum der Funktion einschränkt, nicht aber ihre Lebensdauer. Die Sichtbarkeit einer solchen Variablen verhält sich also wie die einer lokalen Variablen, die Lebensdauer dagegen wie die einer globalen, das heißt, beim Eintritt in die sie umschließende Funktion hat sie exakt denselben Wert wie am Ende des letzten Aufrufs der Funktion. Auf der Implementierungsseite sind dafür keine besonderen Vorrichtungen notwendig: die Variable wird einfach im Aktivierungsblock des Hauptprogramms angelegt.Etwas schwieriger ist die Situation bei objektorientierten Programmiersprachen. Hier ist es normal, dass die Variablen eines Objekts erhalten bleiben auch nach der Ausführung von Methoden, in denen die Variablen sichtbar sind. In diesen Fällen wird die Freigabe von Variablen von einer automatische Speicherbereinigung übernommen, die jedoch nur einwandfrei funktionieren kann, wenn sie nicht durch die Ausführung von systemnahem Programmcode behindert wird.
Namenswahl
Die Wahl von Variablennamen bleibt im allgemeinen völlig dem Programmierer überlassen. Zur Vereinfachung der Nachvollziehbarkeit der Quelltexte empfiehlt es sich, möglichst selbsterklärende Variablennamen zu verwenden, selbst wenn dies zu recht langen Bezeichnern führt. Namenskonventionen für Variablen verbessern die Lesbarkeit von Quelltext. Moderne Editoren haben Vorrichtungen, um den Schreibaufwand auch bei langen Variablennamen zu reduzieren. Nach der Compilation ist der Programmcode weitgehend oder sogar vollständig unabhängig von den verwendeten Variablennamen.
Initialisierung
Variablen sollten vor ihrer Benutzung initialisiert werden, das heißt, einen definierten Wert zugewiesen bekommen. Dies kann durch die Vergabe von Standardwerten durch das Laufzeitsystem der verwendeten Programmiersprache geschehen oder durch explizite Zuweisung eines Wertes an die Variable.
Bei Programmiersprachen, die nicht automatisch alle verwendeten Variablen initialisieren, sind uninitialisierte Variablen eine Quelle für schwer zu findende Fehler: Da die Variable ohne Initialisierung einen zufälligen Wert enthält, wird auch das Verhalten des Programms unvorhersagbar, so dass es manchmal falsche Ergebnisse liefert oder gar abstürzt. Solch ein Fehler ist beim Testen teilweise nur sehr schwer zu entdecken. Enthält der für die Variable reservierte Speicherbereich einen scheinbar erwarteten Inhalt aus einem vorhergehenden Programmdurchlauf, so spricht man auch von einer Speicherinterferenz.
Einzelnachweise
- ↑ Lindholm, Tim; Yellin, Frank: The Java Virtual Machine Specification. Addison-Wesley, 1996: „A variable is a storage location.“
- ↑ Sperber, Michael; Clinger, William; Dybvig, R. K.; Flatt, Matthew; Straaten, Anton van: Revised(5.97) Report on the Algorithmic Language Scheme: „Scheme allows identifiers to stand for locations containing values. These identifiers are called variables.“
- ↑ Perl FAQ 4.3 What's the difference between dynamic and static (lexical) scoping?
Wikimedia Foundation.