3. Variablen

In unserem ersten Programm im Kapitel Einführung sind a, b und summe veränderliche Daten und heißen Variablen. Sie sind Objekte eines vordefinierten Grunddatentyps für ganze Zahlen (int, Kurzform für integer). Der Begriff "Variable" wird für ein veränderliches Objekt gebraucht.

Dabei bestehen Variablen im Wesentlichen aus zwei Teilen: Aus einem Wert und aus einer Adresse, an der der Wert gespeichert wird. Zusätzlich haben Variablen einen Datentyp und können (müssen aber nicht!) einen Namen haben.

3.1. Deklaration von Variablen

Variablen müssen deklariert werden. Die Zeile

int summe, a, b;

ist eine Deklaration. Unter Deklaration wird verstanden, dass der Variablenname dem Compiler bekannt gemacht wird. Wenn dieser Name später im Programm versehentlich falsch geschrieben wird, kennt der Compiler den Namen nicht und gibt eine Fehlermeldung aus. Damit dienen Deklarationen der Programmsicherheit.

3.2. Definition von Variablen

Damit Variablen benutzt werden können, müssen sie auch definiert werden. Die gleiche Zeile wie oben

int summe, a, b;

ist auch gleichzeitig eine Definition der drei Variablen. Unter Definition wird verstanden, dass für die Variablen ein Speicherbereich reserviert wird. Variablen belegen damit Bereiche im Arbeitsspeicher des Computers, deren Inhalte während des Programmlaufs verändert werden können. Die Variablennamen sind also symbolische Adressen, unter denen der Wert gefunden wird und über die auf den Inhalt, also auf den Wert zugegriffen werden kann.

Deklaration und Definition werden unterschieden, weil es auch Deklarationen ohne gleichzeitige Definition geben kann, doch davon später mehr (im Kapitel Funktionen). Zunächst sind die Deklarationen zugleich Definitionen.

Wenn beispielsweise für a eine 100 und für b eine 2 eingegeben wird, sieht der Speicher nach dem Programmlauf wie folgt aus. Dabei sind die Adressen willkürlich gewählt.

 Adresse    Variablenname    Inhalt  
10120     0  
10124     17  
10128    a 100  
10132    b 2  
10136    summe 102  
10140     4009  

Wichtig: Vor C99 mussten Variablen immer am Anfang einer Funktion deklariert und definiert werden.

3.3. Initialisierung von Variablen

Variablen haben nach der Definition einen beliebigen Wert (je nachdem, was vorher in dieser Speicherzelle stand). Sie können vor der Benutzung initialisiert werden, d.h. sie erhalten einen definierten Anfangswert. Dies geschieht grundsätzlich bei der Definition der Variablen.

Beispiel:

Definition der Variablen a, b, c mit gleichzeitiger Initialisierung der Variablen a und b auf die Werte 2 bzw. 3. Auch die Variable c wird initialisiert mit dem Ergebnis der Rechenoperation a + b.

int a = 2, b = 3, c = a + b;

Von der Ausführung her ist dies auf den ersten Blick dasselbe wie die folgenden Zeilen:

int a, b, c;
a = 2;
b = 3;
c = a + b;


Im letzten Fall werden die Variablen a, b und c aber nicht initialisiert, sondern die Werte werden den Variablen zugewiesen! Eine Zuweisung ist also keine Initialisierung und umgekehrt, obwohl in beiden Fällen das Gleichheitszeichen als Operator verwendet wird.

Eine Initialisierung kann nur bei der gleichzeitigen Definition (also der Erzeugung) einer Variablen auftreten, eine Zuweisung setzt immer ein schon vorhandenes Objekt voraus!

Werden dagegen die Variablen a und b nicht initialisiert, ist das Ergebnis von c nicht vorhersehbar. Im allgemeinen geben die Compiler dabei eine Warnung aus, dass nicht initialisierte Variablen im Programm verwendet werden.

3.4. Unveränderliche Variablen und Konstanten

Eine unveränderliche Variable wird mit dem Schlüsselwort const (wird daher auch häufig fälschlicherweise als Konstante bezeichnet) definiert. Dieses Schlüsselwort gibt es erst seit dem C89-Standard. Unveränderliche Variablen müssen bei der Definition sofort initialisiert werden. Denn zu einem späteren Zeitpunkt darf die unveränderliche Variable nicht mehr verändert werden - weder per Zuweisung noch durch Inkrementator oder Dekrementator. Der Compiler prüft dies, um sicherzustellen, dass der Programmierer nicht ausversehens eine unveränderliche Variable ändert. Einige Compiler - wie z.B. gcc - geben allerdings nur eine Warnung aus.

Das Schlüsselwort const kann dabei wahlweise vor oder nach dem Datentypen geschrieben werden. Während die Schreibweise mit const vor dem Datentypen häufig zu sehen ist, wäre die andere Schreibweise deutlich besser für die Lesbarkeit des Programms.

Beispiel:

const int a = 37;
int const b = 25;

a = 5;   /* Fehler! */
b++;     /* Fehler! */


Konstanten werden z.B. durch den Präprozessor-Befehl #define (siehe Kapitel Präprozessor-Befehle) definiert. Konstante haben nur einen Namen und einen Wert; anders als (unveränderliche) Variablen, die zusätzlich noch eine Adresse (einen Speicherort) haben, und anders als Literale, die nur einen Wert haben.

Desweiteren sind die Namen von Arrays und Funktionen auch Konstanten, denn ihre Werte geben an, an welcher Adresse das Array bzw. die Funktion steht, aber sie haben selber keine Adresse.

3.5. Ausdrücke und Werte

Ein Ausdruck ist eine syntaktische Größe - z.B. eine Variable oder ein Literal.

Ein Wert ist eine semantische Größe, die während der Programmausführung ermittelt oder berechnet wird.

Werte werden immer durch Ausdrücke beschrieben oder anders herum gesagt: Ein Ausdruck bezeichnet einen Wert (der unter Umständen erst errechnet werden muss).

3.6. L-Werte und R-Werte

Jede Variable besitzt einen L-Wert und einen R-Wert. Dabei ist die Adresse der Variablen der L-Wert und der Wert der Variable der R-Wert. Die Buchstaben L und R stehen für Links und Rechts und sind relativ zum Zuweisungsoperator zu verstehen. Ein L-Wert steht demnach immer links und ein R-Wert immer rechts vom Zuweisungsoperator.

Beispiel:

int Zahl1 = 1, Zahl2 = 2;

Zahl1 = Zahl2;


Bei dieser Zuweisung wird der Wert der Variablen Zahl2 (also der R-Wert) an der Adresse der Variablen Zahl1 (also der L-Wert) gespeichert.

Ausdrücke, die L-Werte beschreiben, werden L-Ausdrücke und Ausdrücke, die R-Werte beschreiben, werden R-Ausdrücke genannt. Zum Beispiel ist der Name einer Variablen ein L-Ausdruck, während ein Literal ein R-Ausdruck ist.

Ein L-Ausdruck ist dabei mehr als ein R-Ausdruck, denn ein L-Ausdruck beinhaltet L- und R-Werte, während R-Ausdrücke nur R-Werte liefern können. Ein L-Ausdruck liefert automatisch immer dann einen L-Wert, wenn er links vom Zuweisungsoperator steht, und immer dann einen R-Wert, wenn er rechts vom Zuweisungsoperator steht. Im obigen Beispiel sind beide Variablen Zahl1 und Zahl2 L-Ausdrücke. Die Variable Zahl1 steht links vom Gleichheitszeichen, daher wird hier der L-Wert verwendet; die Variable Zahl2 steht aber rechts vom Gleichheitszeichen, daher wird hier der R-Wert verwendet.

R-Ausdrücke sind vor allem Literale, Ausdrücke, die sich mit Operatoren zusammensetzen (außer der verkürzten if-Abfrage ?: (siehe Kapitel Kontrollstrukturen) und dem Variablen-Operator (siehe Kapitel Zeiger), sowie Funktionsaufrufe.

Achtung:
Der Name einer jeden Variablen ist wohl ein L-Ausdruck, aber nicht jeder Name von Variablen darf links vom Zuweisungsoperator stehen: Unveränderlichen Variablen (z.B. const int u;) dürfen keine Werte zugewiesen werden. Trotzdem sind die Namen von unveränderlichen Variablen L-Ausdrücke, da auch unveränderliche Variablen eine Adresse (L-Wert) und einen Wert (R-Wert) haben.

3.7. Gültigkeitsbereich

Der Gültigkeitsbereich (engl. scope) ist der Bereich im C-Quelltext, in dem eine deklarierte Variable sichtbar bzw. bekannt ist. Es wird zwischen folgenden Gültigkeitsbereichen unterschieden (weitere Gültigkeitsbereiche werden im Laufe des Skriptes vorgestellt):

Globale Variablen werden außerhalb von Funktionen - also auch außerhalb der main-Funktion - deklariert (Top Level). Sie sind vom Deklarationspunkt (siehe Abschnitt Deklarationspunkt) bis zum Ende des C-Quelltextes bekannt.

Lokale Variablen (manchmal auch Block-Variablen genannt) werden innerhalb von Funktionen bzw. innerhalb eines Blockes deklariert. Sie sind vom Deklarationspunkt bis zum Ende der Funktion bzw. des Blockes bekannt.

Beispiel:

kap03_01.c

01 int Global;        /* globale Variable */
02
03 int main()
04 {
05    int Lokal;      /* lokale Variable
06                       innerhalb von main*/
07    {
08       int Block;   /* lokale Variable
09                       innerhalb des Blocks */
10       Block = 9;
11    } /* Ende des Gültigkeitsbereiches
12         für die Variable Block! */
13
14    Lokal = 5;
15    Global = 7;
16    Block = 3;      /* Fehler! */
17
18    return 0;
19 } /* Ende des Gültigkeitsbereiches
20      für die Variable Lokal! */
21
22 /* Ende des Gültigkeitsbereiches
23    für die Variable Global! */

Damit dieses Programm ohne Fehler compiliert werden kann, muss die Zeile 16 auskommentiert werden!

3.8. Deklarationspunkt

Eine Variable kann im Normalfall immer erst dann verwendet werden, wenn sie komplett deklariert ist. Um festzulegen, wann eine Variable komplett deklariert ist, wird ein sogenannter Deklarationspunkt definiert: Eine Variable ist komplett deklariert nach dem letzten Token des Variablennamens. Danach - also auch innerhalb der gleichen Zeile - kann die Variable verwendet werden.

Beispiel:

int intsize = sizeof(intsize);
/*         ^
   Deklarationspunkt
*/


In diesem Beispiel kann die Variable intsize mit ihrer eigenen Größe initialisiert werden, da die Initialisierung hinter dem Deklarationspunkt liegt.

3.9. Sichtbarkeit

Eine Deklaration einer Variable ist überall innerhalb seines Gültigkeitsbereiches sichtbar. Die Sichtbarkeit kann aber durch eine Deklaration einer weiteren, gleichnamigen Variable überlappen, so dass die erste Variable nicht mehr sichtbar ist; sie ist sozusagen versteckt. Voraussetzung ist, dass die zweite Variable innerhalb eines Blocks deklariert wird, der im Gültigkeitsbereich der ersten Variable liegt.

Beispiel:

int Var;        /* globale Variable */

int main()
{
   float Var;   /* damit wird die obige
                   Variable "versteckt" */
   ...
}


Eine Variable ist immer erst ab der Stelle gültig, an der sie deklariert wird - und nicht ab Beginn des Blockes, in dem sie deklariert ist. Dadurch kann es - wie im folgenden Beispiel - zu Situationen kommen, in denen innerhalb eines Blockes gleichnamige Variablen auf unterschiedliche Deklarationen zurückzuführen sind. Nach Standard-C ist dies durchaus korrekt, aber die Verwendung gleichnamiger Variablen innerhalb eines Blockes ist ein schlechter Programmierstil!

Beispiel:

kap03_02.c

01 #include <stdio.h>
02
03 int Var;        /* globale Variable */
04
05 int main()
06 {
07    Var = 1;     /* gemeint ist die
08                    globale Variable */
09    printf("globale Variable Var = %i\n", Var);
10
11    float Var;   /* lokale Variable  */
12
13    Var = 1.5;   /* jetzt ist die
14                    lokale Variable gemeint */
15    printf("lokale Variable Var = %f\n", Var);
16
17    return 0;
18 }


3.10. Speicherklassen

Die Angabe einer Speicherklasse bestimmt den Gültigkeitsbereich mit und gibt ferner an, ob die deklarierte Variable auch für den Linker bekannt sein soll. Es gibt die folgenden fünf Speicherklassen:

 auto

Kann nur bei der Deklaration von Variablen innerhalb von Funktionen und Blöcken verwendet werden. Die deklarierte Variable ist dadurch eine lokale (automatische) Variable und ist nur innerhalb des Blockes gültig, in dem sie deklariert wurde (vom Deklarationspunkt bis zum Ende des Blockes). Da diese Speicherklasse der Standard ist, kann das Schlüsselwort auto auch weggelassen werden - das ist auch der Grund, warum dieses Schlüsselwort nur selten in C-Programmen zu finden ist.

Seit dem C++11-Standard hat dieses Schlüsselwort eine neue Bedeutung erhalten; daher wird dringend empfohlen, diese Speicherklasse in C nicht mehr zu verwenden.

 extern

Kann für globale oder lokale Variablen verwendet werden. Durch dieses Schlüsselwort wird angegeben, dass die deklarierte Variable in einer anderen C-Quelltextdatei deklariert und definiert ist. Erst beim Linken wird die deklarierte Variable mit der definierten Variable (aus einer anderen C-Quelltextdatei) verbunden. Standardmäßig werden externe Variablen als globale Variablen deklariert und sind daher innerhalb der ganzen Quelltextdatei (vom Deklarationspunkt bis zum Ende der Quelltextdatei) gültig. Als lokale Variablen sind externe Variablen auch nur innerhalb des Blockes gültig, in dem sie deklariert wurden. Einige C-Compiler, die sich nicht ganz an das Standard-C halten, sehen alle externen Variablen als globale Variablen.

 register

Kann nur bei der Deklaration von lokalen Variablen oder bei Funktions-Parametern verwendet werden. Der Compiler bekommt damit den Hinweis, dass diese Variable häufig genutzt wird und dass die Variable - wenn möglich - so definiert werden sollte, dass die Zugriffszeit möglichst gering ist. Der schnellste Zugriff wird erreicht, wenn die Variable in einem Register des Prozessors definiert wird.

 static

Kann für globale oder lokale Variablen verwendet werden. Statische Variablen sind wohl nur innerhalb des Blockes (lokal) bzw. innerhalb der Quelltextdatei (global) sichtbar, in denen sie deklariert wurden; sie sind dann aber bis zum Ende des Programms gültig! D.h. am Ende des Gültigkeitsbereiches von normalen Variablen werden statische Variablen nicht vernichtet und behalten sogar ihren Wert bei. Statische Variablen mit Initialisierung werden dadurch auch nur beim einmaligen Anlegen initialisiert; wird der Block mit der statischen Variablen-Deklaration erneut aufgerufen, existiert diese Variable bereits und die Initialsierung wird nicht durchgeführt.

Beispiel:

kap03_03.c

01 #include <stdio.h>
02
03 int main()
04 {
05    int i;
06
07    for (i = 0; i < 5; i++)
08    {
09       static int Statisch = 0;
10       int NichtStatisch = 0;
11
12       Statisch = Statisch + 1;
13       NichtStatisch = NichtStatisch + 1;
14       printf("%i: Statisch = %d;", i, Statisch);
15       printf("NichtStatisch = %d\n", NichtStatisch);
16    }
17
18    return 0;
19 }


Der Anweisungsblock der Schleife wird fünf Mal durchlaufen; dabei werden folgende Werte ausgegeben:

   0: Statisch = 1; NichtStatisch = 1
   1: Statisch = 2; NichtStatisch = 1
   2: Statisch = 3; NichtStatisch = 1
   3: Statisch = 4; NichtStatisch = 1
   4: Statisch = 5; NichtStatisch = 1


Statische Variablen werden ferner nicht dem Linker bekannt gegeben, d.h. es können keine externen Verweise auf statische Variablen deklariert werden.

 typedef

Anders als bei den anderen vier Speicherklassen wird mit diesem Schlüsselwort ein neuer Datentyp aus vorhandenen Datentypen definiert. Anstelle eines Variablennamens wird der Name des neuen Datentyps angegeben.

Beispiel:

typedef int GanzeZahl; /* GanzeZahl ist jetzt ein Datentyp  */
GanzeZahl i;           /* Variable i vom Datentyp GanzeZahl */


3.11. volatile

Mit dem Schlüsselwort volatile (engl. für flüchtig) bei der Variablendefinition wird dem C-Compiler mitgeteilt, dass der Wert dieser Variable auch von außerhalb des Programms - also von anderen Programmen bzw. von der Hardware - geändert werden kann. Solche Variablen werden bei der Optimierung des Quellcodes vom Compiler nicht berücksichtigt.

Am häufigsten wird dieser Typspezifizierer beim Zugriff auf Speicheradressen der Computer-Hardware verwendet, da die Hardware den Wert der Speicheradresse unabhängig vom Programm ändern kann (z.B. Ein- und Ausgabepuffer sowie Kontrollregister).



Voriges Kapitel: 2. Zeichen, Zeichensätze und Tokens