7.11 Globale, lokale und statische Variablen
Normalerweise verwenden Sie beim Programmieren viele Variablen in Funktionen oder in Anweisungsblöcken. Mit globalen Variablen, die gleich am Anfang des Programms deklariert werden, sollten Sie hingegen sparsam umgehen. Machen Sie sich auf jeden Fall mit dem Bezugsrahmen von Variablen vertraut. Grundlegend unterscheidet man zwischen einem lokalen und einem globalen Bezugsrahmen. Globale Variablen sind Variablen, die im gesamten Programm sichtbar sind, lokale Variablen sind nur in der Funktion sichtbar, in der sie definiert wurden und verlieren beim Verlassen der entsprechenden Funktion ihre Gültigkeit.
7.11.1 Lokale Variablen
Gibt es sowohl eine globale als auch eine lokale Variable mit demselben Bezeichner, erhält bei der Verwendung die Variable den Zuschlag, die bei der Ausführung im engsten Sinne lokal ist. Zum Beispiel gewinnt eine Variable aus demselben innersten Codeblock gegen eine Variable, die lediglich in derselben Funktion oder in einem weiter außen gelegenen Codeblock deklariert wurde. Folgender Codeausschnitt soll diese zeigen:
00 // Kapitel7/lokale_variablen.c
01 #include <stdio.h>
02 int main(void) {
03 int ival = 123;
04 printf("ival: %d\n", ival);
05 { // Neuer Anweisungsblock
06 ival = 321;
07 printf("ival: %d\n", ival);
08 }
09 printf("ival: %d\n", ival);
10 { // Neuer Anweisungsblock
11 int ival = 456;
12 printf("ival: %d\n", ival);
13 }
14 printf("ival: %d\n", ival);
15 return 0;
16 }
Hier das Programm bei der Ausführung:
ival: 123
ival: 321
ival: 321
ival: 456
ival: 321
Sie weisen zunächst in Zeile (03) der Variablen ival den Wert 123 zu, was die Ausgabe in Zeile (04) dann auch bestätigt. In den Zeilen (05) bis (08) wird ein neuer Anweisungsblock verwendet, in dem in Zeile (06) der Wert von ival auf 321 geändert wird. Das bestätigt die Ausgabe in Zeile (07). Außerhalb des Anweisungsblocks in Zeile (09) wird ebenfalls der Wert 321 ausgegeben. In den Zeilen (10) bis (13) wird nochmals ein Anweisungsblock verwendet, nur wird jetzt in Zeile (11) eine neue lokale Variable ival definiert und mit dem Wert 456 initialisiert. Das bestätigt die Ausgabe in Zeile (12). Die in Zeile (11) definierte Variable ist allerdings nur innerhalb ihres eigenen Anweisungsblocks gültig und überdeckt in diesem Block die gleichnamige Variable außerhalb ihres Anweisungsblocks. Dieses bestätigt wieder die Ausgabe in Zeile (14). Dort ist der Wert von ival nach wie vor 321. Zwar wurde in diesem Beispiel nur die main()-Funktion verwendet, aber das Prinzip der lokalen Variablen ist allgemeingültig. Einige Compiler geben hierbei auch eine Warnmeldung dieser gleichnamigen Überdeckung in Zeile (11) aus.
Regel für lokale Variablen
Für lokale Variablen gilt Folgendes: Bei gleichnamigen Variablen innerhalb derselben Funktion ist immer die »lokalste« Variable gültig, also die Variable, die dem Anweisungsblock am nächsten steht. In der Praxis wird man allerdings eher selten hergehen und gleichnamige Variablen in derselben Funktion verwenden. Das Beispiel dient hier nur dem Zweck, den lokalen Bezugsrahmen zu demonstrieren.
7.11.2 Globale Variablen
Während Sie mit lokalen Variablen auf die Funktionen bzw. auf den Anweisungsblock selbst beschränkt sind, können Sie globale Variablen in allen Funktionen verwenden. Hierzu folgt zunächst ein einfaches Beispiel mit einer globalen Variablen, die in Zeile (02) definiert wurde:
00 // Kapitel7/globale_variablen.c
01 #include <stdio.h>
02 int ival=789;
03 void funktion1(void) {
04 printf("ival: %d\n", ival);
05 }
06 void funktion2(void) {
07 int ival = 111;
08 printf("ival: %d\n", ival);
09 }
10 int main(void) {
11 int ival = 123;
12 printf("ival: %d\n", ival);
13 { // Neuer Anweisungsblock
14 ival = 321;
15 printf("ival: %d\n", ival);
16 }
17 printf("ival: %d\n", ival);
18 { // Neuer Anweisungsblock
19 int ival = 456;
20 printf("ival: %d\n", ival);
21 }
22 printf("ival: %d\n", ival);
23 funktion1();
24 funktion2();
25 return 0;
26 }
Das Programm gibt bei der Ausführung Folgendes aus:
Die Ausgabe des Programms dürfte vielleicht den einen oder anderen Leser etwas überraschen. Obwohl in Zeile (02) eine globale Variable ival definiert wurde, kommt sie nur einmal in der Funktion funktion1() in Zeile (03) bis (05) zum Einsatz. Ansonsten entspricht die Ausführung dem Abschnitt 7.11, nur dass hier noch eine Funktion funktion2() definiert wurde (Zeile (06) bis (09)). Sie verwendet allerdings auch nur eine darin definierte Variable ival (Zeile (07)).
Auch wenn eine globale Variable definiert wird und eine gleichnamige lokale Variable existiert, erhält immer auch hier die lokalste Variable (die dem Anweisungsblock am nächsten steht) den Zuschlag und überdeckt die Variable, die außerhalb des Blockes steht. Existieren in einem Programm unwissentlich zwei gleichnamige Variablen, nämlich eine globale und eine lokale, kann es zu unerwarteten Ergebnissen kommen. Die Lebensdauer einer globalen Variablen erstreckt sich bis zum Programmende.
Vermeiden Sie globale Variablen, wenn möglich
Da globale Variablen für alle Funktionen sichtbar sind, sollten Sie die Verwendung solcher Variablen stets gut überdenken. Sie sollten sich immer fragen, ob es nicht ohne auch globale Variablen geht. Wenn mehrere Funktionen auf eine globale Variable zugreifen, kann es hier schnell zu einer ungewollten Veränderung des Wertes kommen. Dabei wird es dann häufig sehr schwierig, den Fehler bzw. die Funktion, die den Fehler verursacht hat, zu lokalisieren. Natürlich geht es nicht immer ohne globale Variablen, z. B. wenn Sie einen Mikrocontroller verwenden, der nur sehr wenig Stapelspeicher besitzt. Unser Tipp an dieser Stelle lautet: Wenn Sie globale Variablen verwenden müssen, kennzeichnen Sie dies im Variablennamen und stellen z. B. G voran.
Globale Variablen werden meist, im Gegensatz zu lokalen Variablen, bei der Definition automatisch mit 0 initialisiert. Dies muss aber nicht so sein, also verlassen Sie sich besser nicht darauf.
7.11.3 Speicherklasse static
Wenn Sie bei lokalen bzw. globalen Variablen die Speicherklasse static verwenden, wird die Variable im Datensegment mit einer festen Speicheradresse versehen, die bis zum Programmende erhalten bleibt. Vor allem bei der Programmierung von Betriebssystemfunktionen wird oft so verfahren. Der Bezugsrahmen hängt in diesem Fall davon ab, ob Sie die Speicherklasse in einem lokalen oder globalen Bereich vereinbaren.
Lokale static-Variable innerhalb einer Funktion
Verwenden Sie eine static-Variable lokal innerhalb einer Funktion, bleibt der Wert der lokalen static-Variablen nach der Rückkehr aus dieser Funktion erhalten. Trotzdem können Sie diese static-Variable nur lokal innerhalb der Funktion ansprechen, in der sie erstellt wurde. Ein einfaches Programmbeispiel hierzu, in dem eine Variable mit dem Speicherklassen-Spezifizierer static gekennzeichnet wurde, ist:
00 // Kapitel7/static_beispiel.c
01 #include <stdio.h>
02 void counter(void) {
03 static int ival;
04 printf("ival: %d\n", ival);
05 ++ival;
06 }
07 int main(void) {
08 counter();
09 counter();
10 counter();
11 return 0;
12 }
Die Ausgabe des Listings ist hier:
ival = 0
ival = 1
ival = 2
Dank dem Schlüsselwort static in Zeile (03) werden in der Funktion die Werte 0, 1 und 2 ausgegeben, obwohl diese Funktion dreimal hintereinander neu aufgerufen wird. Daher können Sie sich darauf verlassen, dass bei der Beendigung einer Funktion eine statische Variable nicht den Wert verliert. Das liegt daran, dass statische Variablen in einer Funktion nicht wie üblich im Stack-Segment gespeichert werden, sondern in einer festen Speicheradresse im Datensegment. Dadurch hat die Variable eine Lebensdauer bis zum Programmende. Ohne static hätte die Variable nur eine Gültigkeit innerhalb der Funktion, sowie eine begrenzte Lebenszeit während der Laufzeit der Funktion. Des Weiteren können Sie sicher sein, wie das letzte Beispiel zeigt, dass eine static-Variable stets automatisch mit 0 initialisiert wird.
static-Variable außerhalb einer Funktion
Deklarieren Sie hingegen eine static-Variable außerhalb einer Funktion, ist sie nur in der Datei gültig, in der sie deklariert wurde – sprich, die Variable kann von keiner anderen Programmdatei verwendet werden. Innerhalb der Programmdatei können Sie diese Variable wie eine gewöhnlich globale Variable verwenden. Haben Sie beispielsweise in zwei Quelldateien, die zu einem Programm gehören, die folgende Variable verwendet,
// datei-01.c
int ival = 12345;
// datei-02.c
int ival = 34567;
beschwert sich der Linker, dass die globale Variable ival mehr als nur einmal im Programm definiert wurde. Stellen Sie jetzt in jeder der beiden Quelldateien das Schlüsselwort static davor,
// datei-01.c
static int ival = 12345;
// datei-02.c
static int ival = 34567;
übersetzt der Linker das Programm anstandslos, weil jetzt die globalen static-Variablen nur in der jeweiligen Quelldatei gültig und sichtbar sind. Allerdings soll ein solches hier gezeigtes Beispiel mehrere Definitionen mit dem gleichen Bezeichner keine Schule machen, sondern es dient auch hier nur zur Demonstration von static. Vielmehr sollten Sie globale Variablen, die nicht von anderen Modulen verwendet werden sollen, davor schützen, indem Sie diese mit dem Schlüsselwort static deklarieren.
static-Funktionen
Dasselbe wie für eine globale static-Variable gilt für static-Funktionen. Auch Funktionen, die Sie mit static kennzeichnen, sind nur in der aktuellen Quelldatei gültig, in der sie definiert wurden. So können Sie durchaus zwei Funktionen mit denselben Bezeichnern in unterschiedlichen Quelldateien verwenden, wenn Sie mindestens eine davon mit static spezifiziert haben. Auf diese Weise können Sie beispielsweise verhindern, dass eine Funktion von einer anderen Datei aufgerufen wird und eben nur innerhalb einer einzigen Datei verwendet werden kann.
7.11.4 Die Speicherklasse extern
Bezogen auf globale Variablen dürfte dann hier auch noch die Speicherklasse extern von Bedeutung sein. Mit dem Schlüsselwort extern gibt der Compiler dem Linker Bescheid, dass dieser die Verweise in einer anderen Übersetzungseinheit (Quell- oder Header-Datei) und/oder Bibliothek auflösen muss. Sie deklarieren hiermit nur eine Variable, und die Definition erstellen Sie für gewöhnlich in einer anderen Datei.
Verwenden Sie bei einer Funktionsdeklaration (Prototyp) nicht den Speicherklassen-Spezifizierer extern, wird sie implizit automatisch vom Compiler so behandelt, als enthielte sie das Schlüsselwort extern. Somit müssen Sie zwar nicht zwangsläufig bei einer Funktionsdeklaration das Schlüsselwort extern verwenden, aber es ist auch nicht falsch, weil Sie hiermit immer eine Deklaration erzwingen und es nie eine Definition wird, bei der der Compiler Speicherplatz reserviert.