Sie wissen, daß eine in einer Funktion definierte Variable
eine lokale Variable ist, deren Gültigkeitsbereich sich lediglich
auf diese Funktion erstreckt. Ihr Wert ist zunächst undefiniert,
bis ihr explizit einer zugewiesen wird.
Sehen Sie sich einmal die folgende Funktionsdefinition an:
long test(long n)
{
long x;
if (n>0) x=n;
return x;
}
|
Der Funktion test wird ein long-Parameter übergeben.
Ist er größer als 0, wird er der lokalen Variablen x zugewiesen.
Sodann wird der Wert von x zurückgegeben.
Was das für einen Sinn haben soll, sei einmal zurückgestellt.
Sicher sehen Sie aber schon, welches Problem sich hier ergibt:
Ist der Parameter n nälich kleiner oder gleich 0, erhält
x gar keinen Wert und ist demzufolge undefiniert.
Bei den Aufrufen
printf("Erster Aufruf: %ld\n", test(8));
printf("Erster Aufruf: %ld\n", test(-1));
|
wird im ersten Fall der Wert 8 ausgegeben, im zweiten Fall aber ein
undefinierter, nicht vorhersagbarer Wert.
Definition statischer Variabler
In C gibt es aber auch die Möglichkeit,
lokale Variablen einer Funktion als statisch zu vereinbaren.
Das bedeutet, daß sie nach Abgabe der
Kontrolle an den aufrufenden Programmteil ihre Werte behalten.
Beim nächsten Aufruf stehen sie dann wieder zur Verfügung. Damit
sind z.B. Funktionen möglich, die bei jedem Aufruf ein anderes
Ergebnis liefern, selbst wenn die Argumente identisch sind.
Wie wird das gemacht?
Ganz einfach, indem der üblichen Definition einer Variablen
das Wort static vorangestellt wird.
Die Anweisung static long x vereinbart also eine statische
long-Variable x. Mit dieser kleinen Änderung sieht
unsere Funktion von eben wie folgt aus:
long test(long n)
{
static long x;
if (n>0) x=n;
return x;
}
|
Was ist denn damit erreicht? Nun, wenn als Parameter n
ein positiver Wert übergeben wird, funktioniert alles wie bisher.
Wird aber ein positiver Wert oder 0 übergeben, hat die Variable x
den Wert, den sie beim letzten Aufruf hatte.
Das heißt also, bei den Aufrufen
printf("Erster Aufruf: %ld\n", test(8));
printf("Erster Aufruf: %ld\n", test(-1));
|
ergibt sich nun ein anderes Bild. Im ersten Fall wird wie bisher
der Wert 8 ausgegeben, im zweiten Fall auch. Durch die Definition von
x als statische Variable merkt sich die Funktion test
den Wert von x.
Initialisierung statischer Variabler
So weit, so gut. Was passiert aber, wenn die Aufrufe der Funktion
anders herum erfolgen. Also etwa so:
printf("Erster Aufruf: %ld\n", test(-1));
printf("Erster Aufruf: %ld\n", test(8));
|
Es wird beim ersten Aufruf ein undefinierter Wert ausgegeben.
Ganz klar: Die Variable x hat ja noch nie einen definierten
Wert erhalten, den sie sich hatte merken können.
Erst beim zweiten Aufruf geschieht das, und es wird der Wert 8 ausgegeben.
Was also noch fehlt, ist eine ordentliche Initialisierung. Etwa wie folgt:
long test(long n)
{
static long x=12;
if (n>0) x=n;
return x;
}
|
Nun erhält die Variable x beim ersten Aufruf den Wert 12.
Im Gegensatz zur Initialisierung einer normalen lokalen Variablen
wird diese Initialisierung aber wirklich nur beim ersten Aufruf
durchgeführt wird.
Das heißt, die Abfolge
printf("Erster Aufruf: %ld\n", test(-1));
printf("Erster Aufruf: %ld\n", test(8));
printf("Erster Aufruf: %ld\n", test(-1));
|
gibt nacheinander die Werte 12, 8 und noch einmal 8 aus. Beim ersten
Aufruf durch die Initialisierung mit 12, beim zweiten durch den positiven
Parameter 8, beim dritten durch das "Merken" des letzten Wertes.
Ein Zufallszahlengenerator
So weit war es nun ein bißchen theoretisch. Jetzt stellt sich
die Frage, wer überhaupt Interesse an dieser Fähigkeit hat,
und welche Anwendungen es gibt. Das Standardbeispiel ist der
Zufallszahlengenerator, und den wollen wir uns nun einmal ansehen.
Bei einem Zufallszahlengenerator im üblichen Sinn handelt es sich
um eine Funktion, die eine Folge von Zahlen liefert. Die Elemente
dieser Folge sollen natürlich verschieden sein, das legt die
Benutzung von statischen Variablen nahe. Zudem sollen die Zahlen
gleichverteilt in einem vorgegebenen Intervall liegen. Ist dieses
Intervall beispielsweise der Bereich von 1 bis 30000, so soll jede
dieser Zahlen mit gleicher Häufigkeit in der Folge auftauchen.
Diese Frage ist aber eher mathematischer Natur und soll hier
nicht interessieren.
Es ist nicht ohne weiteres möglich, "echte" Zufallszahlen zu
erzeugen. Aus diesem Grunde behilft man sich meist mit einer
Folge, für deren Definition es zwei Möglichkeiten gibt. Entweder
die Werte x[i] entstammen einer Funktion x[i]=f(i), die möglichst
kompliziert gewählt werden sollte. Oder Sie definieren einen
Anfangswert x[1] und erklären die weiteren Werte der Folge
rekursiv durch Anwendung einer Funktion auf den vorherigen Wert,
also x[i]=f(x[i-1]). Die Elemente der resultierenden Folge nennt
man "Pseudo-Zufallszahlen".
Bei unserer Realisierung haben wir uns für die zweite
Möglichkeit entschieden:
int main()
{
long i, s;
scanf("%ld",&s);
printf("%ld\n",random(s));
for ( i=0; i<100;i ++ )
printf("%ld\n",random(0));
}
long random(long n)
{
static long x=307;
if (n>0) x=n;
else x=(x*x)%32003-1;
return x;
}
|
Betrachten Sie die Funktion random. Als
Anfangswert wird gesetzt x[1]=307, der Wert x[i] wird durch
x[i]=(x[i-1]*x[i-1])%32003-1 erhalten, % ist der mod-Operator.
Indizes sind nicht zu berücksichtigen. Da Sie nämlich immer nur
auf den letzten Wert x[i-1] zurückgreifen, kommen Sie mit einer
einzigen Variablen x aus. Alle vorherigen Werte werden also
gewissermaßen "vergessen". Die Formel lautet somit einfacher:
x=(x*x)%32003-1.
Wie bei professionellen Generatoren üblich, erhält der Benutzer
die Möglichkeit, auf die Folge selbst einzuwirken, indem nämlich
der Anfangswert von außen gesetzt werden kann. Dazu dient der
Parameter n. Ist er größer als 0, wird x=n gesetzt und mit diesem
Wert fortgefahren. Ansonsten bleibt der alte Wert von x erhalten.
Was geschieht also beim Aufruf von random?
Nun, handelt es sich um den ersten Aufruf, wird zuallererst x=307 gesetzt.
Dann erfolgt die Abfrage n>0.
Ist dies erfüllt, wird x=n gesetzt, im anderen
Fall erfolgt keine Aktion. Auf jeden Fall wird nun der Ausdruck
(x*x)%32003-1 ausgewertet, das Resultat an x zugewiesen. x selbst
wird zurückgegeben. Auf diese Art laufen auch alle weiteren
Aufrufe von random ab. Es besteht jedoch der winzige Unterschied,
daß die Zuweisung x=307 nun nicht mehr erfolgt.
Im Hauptprogramm wird einfach ein Anfangswert abgefragt, sodann
die ersten hundert Elemente der daraus entstehenden Folge auf dem
Bildschirm ausgegeben.
Statische globale Variablen
Nachdem Ihnen nun die prinzipielle Anwendung statischer Variabler
klar ist, sollte erwähnt werden, daß das Wort static auch in
Zusammenhang mit externen Variablen benutzt werden kann. Dabei
sind solche Variablen gemeint, die außerhalb jeder Funktion
definiert sind, also als global zu betrachten sind. Setzt man
diesen das Wort static voran, schränkt man deren Gültigkeit auf
die Quelldatei ein, in der diese Definition steht. In diesem
Artikel interessieren uns aber nur lokale statische Variablen, wie
sie bei unserem Zufallszahlengenerator auftraten.
Globale Variablen
Fragen wir nun: Was wäre, wenn es in C keine statischen Variablen
gäbe? Könnten wir einen Zufallszahlengenerator auch
ohne diese programmieren? Selbstverständlich lautet die Antwort:
ja. Denn lokale statische Variablen sind nichts anderes als
globale Variablen, deren Gültigkeitsbereich auf eine einzige
Funktion beschränkt wurde. Im Gegensatz zu "normalen" lokalen
Variablen ist jedoch die Lebensdauer mit der Laufzeit des gesamten
Programms identisch.
Eine Simulation von statischen Variablen ist also durch globale
Variablen prinzipiell möglich. Dann muß jedoch verlangt werden,
daß diese von keinem anderen Unterprogramm verändert werden
dürfen. Dies ist zweifellos schwer zu kontrollieren, besonders
wenn man mit Bibliotheken arbeitet. Zudem wäre es nicht möglich,
derartige Unterprogramme als unveränderliche Bibliotheksprogramme
zu schreiben. Denn je nach Zusammensetzung des Gesamtprogramms
müßten die Namen der betreffenden globalen Variablen geändert
werden.
Zusammenfassung
Fassen wir also zusammen: Statische lokale Variablen finden
Anwendung bei der Programmierung von Zufallszahlengeneratoren und
Betriebssystemen. In beiden Fällen können sie globale Variablen
ersetzen. Das Hauptprogramm wird somit von Verwaltungsaufwand
entlastet. Zudem wird es übersichtlicher, weil nicht eine große
Zahl globaler Variabler zur Speicherung des Zustandes der
Unterprogramme vorhanden sein müssen.
|