Eine Zeigervariable ist eine Variable, die einen Zeiger darstellt. Anstelle dieses Begriffs sind auch die Begriffe „Zeiger“, „Pointer“ oder „Pointer-Variable“ verbreitet. Eine Zeigervariable wird dadurch definiert, dass man nach dem Datentyp T als ptr-operator einen * angibt:
T * p
Der Datentyp von p ist dann „Zeiger auf T“ und wird auch mit „T*“ abgekürzt.
Beispiel 1a: Durch
int* pi; double * pd; char*pc; AnsiString *pas;
werden die Zeigervariablen pi, pd und pc definiert. Sie haben die Datentypen „Zeiger auf int“, „Zeiger auf double“ und „Zeiger auf char“
bzw. kürzer int*, double* und char*.
Ein Zeiger stellt eine Hauptspeicheradresse dar. Wenn der Zeiger p den Datentyp „Zeiger auf T“ hat, spricht man mit *p die sizeof(T) Bytes ab der Adresse in p als Ausdruck des Datentyps T an. Der Operator * wird auch als Dereferenzierungsoperator
bezeichnet.
Beispiel 1b: Nach den Definitionen aus dem letzten Beispiel werden die 4 (=sizeof(int)) Bytes ab der Adresse in pi mit *pi als Variable des Datentyps int angesprochen. Durch
*pi=17;
werden diese 4 Bytes mit dem Wert 17 überschrieben.
Falls der Datentyp T eine Klasse ist,
Kontobewegung* pk = new Kontobewegung;
kann man ein Element der Klasse sowohl über den dereferenzierten Zeiger
(*pk).KontoNr =17;
als auch über den Pfeiloperator →
pk→KontoNr =17;
ansprechen. Der Ausdruck e1→e2 wird vom Compiler in (*(e1)).e2 umgewandelt.
Ein Zeiger auf die dereferenzierte Variable wird oft durch einen Pfeil dargestellt:
p ——→ *p
Mit der Definition einer Zeigervariablen p wird nur der Speicher für die Zeigervariable selbst reserviert, nicht jedoch der für die Variable *p, auf die er zeigt. Eine Zeigervariable belegt bei einem 32-bit-Betriebssystem immer 4 Bytes.
Eine global definierte Zeigervariable p wird wie jede andere globale Variable mit 0 initialisiert, und eine lokal definierte Zeigervariable hat wie jede andere lokale Variable einen unbestimmten Wert. Deshalb stellt *p den Speicherbereich an der Adresse 0 bzw. an einer undefinierten Adresse dar, wenn p keine Adresse zugewiesen wird.
Beispiel 1c: Vergisst man nach der Definition einer Zeigervariablen p, dieser die Adresse eines reservierten Speicherbereichs zuzuweisen, wird durch *p meist ein nicht reservierter Speicherbereich angesprochen:
int* pi;
*pi=17;
Mit etwas Glück führt die Zuweisung dann zu einer Zugriffsverletzung, die vom Betriebssystem erkannt wird und zu einem Programmabsturz führt. Mit etwas weniger Glück erhält man keine Fehlermeldung und überschreibt Daten, was in ganz anderen Teilen des Programms unerwartete Folgen haben kann. Die Ursache für solche Fehler ist oft nur schwer zu finden.
Damit unterscheidet sich eine Variable, die über einen Zeiger angesprochen wird, folgendermaßen von einer „gewöhnlichen“ Variablen, die durch eine Definition erzeugt wurde:
int i;automatisch mit ihrer Definition. Dieser Speicherbereich wird automatisch durch den Namen der Variablen angesprochen und ist untrennbar mit diesem Namen verbunden. Es ist nicht möglich, bei der Verwendung solcher Variablen eine Zugriffsverletzung zu bekommen, da sie nie Speicherbereiche ansprechen, die nicht reserviert sind.Die Adresse dieses Speicherbereichs ist nicht wie unter 1. fest mit dem Namen der Variablen verbunden, sondern der Wert der Variablen p. Nach einer Änderung dieser Adresse stellt *p einen anderen Speicherbereich als vorher dar.
Im Umgang mit Zeigern ist deshalb mehr Vorsicht geboten als mit „gewöhnlichen“ Variablen.
Bei der Definition einer Zeigervariablen muss das Zeichen * nicht unmittelbar auf den Datentyp folgen. Die folgenden vier Definitionen sind gleichwertig:
int* i; // Whitespace (z.B. ein Leerzeichen) nach * int *i; // Whitespace vor * int * i; // Whitespace vor * und nach * int*i; // Kein whitespace vor * und nach *
Versuchen wir nun, diese vier Definitionen nach demselben Schema wie eine Definition von „gewöhnlichen“ Variablen zu interpretieren. Bei einer solchen Definition bedeutet
T v;
dass eine Variable v definiert wird, die den Datentyp T hat. Für die vier gleichwertigen Definitionen ergeben sich verschiedene Interpretationen, die als Kommentar angegeben sind:
int* i; // Definition der Variablen i des Datentyps int* int *i; // Irreführend: Es wird kein "*i" definiert, // obwohl die dereferenzierte Variable *i heißt. int * i; // Datentyp int oder int* oder was? int*i; // Datentyp int oder int* oder was?
Offensichtlich passen nur die ersten beiden in dieses Schema. Die erste führt dabei zu einer richtigen und die zweite zu einer falschen Interpretation.
Allerdings passt die erste Schreibweise nur bei der Definitionen einer einzelnen Zeigervariablen in dieses Schema, da sich der * bei einer Definition nur auf die Variable unmittelbar rechts vom * bezieht:
int* i,j,k; // definiert int* i, int j, int k // und nicht: int* j, int* k
Zur Vermeidung solcher Missverständnisse sind zwei Schreibweisen verbreitet:
int *i,*j,*k; // definiert int* i, int* j, int* k