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
können (müssen aber nicht!) Variablen 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: In C müssen 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.
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).