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. |
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.
01 #include <stdio.h>
Der Anweisungsblock der Schleife wird fünf Mal durchlaufen; dabei werden folgende
Werte ausgegeben: |
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. |
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
Nächstes Kapitel: 4. Datentypen in C