Abruf

Einführung in den Datentyp Zeiger

Warum brauchen wir einen neuen Datentyp?

Mit den Datentypen, die wir in PASCAL bisher bereits kennen, können wir eine große Anzahl an Problemstellungen lösen. Es gibt aber auch Aufgaben, bei denen diese Datentypen nicht mehr ausreichen. Ein Beispiel: Als Softwarefirma sollen wir ein Programm zur Verwaltung der Daten von Oberstufenschülern schreiben. Das Programm soll bei uns kompiliert und dann in fertigem Zustand verkauft werden. Wie speichern wir die Daten? Von den uns bekannten Datentypen kommt nur der Datentyp Reihung (Array) in Frage. Wenn wir aber z.B. für die Namen ein Feld vereinbaren, müssen wir beim Erstellen des Programms eine maximale Anzahl an Schülern vorgeben. Wenn wir diese Grenze sehr knapp wählen, funktioniert das Programm bei einigen Schulen vielleicht nicht, weil sie zu viele Schüler haben; wenn wir die Grenze sehr groß wählen, wird bei allen Schulen viel Speicherplatz verschwendet. Das entscheidende Problem hierbei ist, dass Reihungen - wie alle uns bisher bekannten Datentypen - statische Variablen sind, die bereits im Quelltext des Programms vereinbart werden müssen. Im Gegensatz dazu wären natürlich dynamische Variablen, die wir erst während der Laufzeit des Programms nach Bedarf einrichten, die Lösung für das Problem. Um dynamische Datenstrukturen (wie z.B. Listen mit einer sich beliebig verändernden Anzahl an Elementen) umsetzen zu können, benötigen wir den neuen Datentyp Zeiger. Bei dieser Einführung des Datentyps werde ich leider noch nicht zu derartigen dynamischen Datenstrukturen, die der eigentliche Sinn des Einsatzes von Zeigern sind, kommen. Das Folgende ist aber die Grundlage für diese späteren Anwendungsmöglichkeiten.

Das Grundprinzip eines Zeigers

Eine Zeigervariable ist eine Variable, die die Adresse einer anderen Variablen - der sogenannten Bezugsvariablen enthält. Um auf die Bezugsvariable zuzugreifen, braucht diese dann keinen eigenen Bezeichner ("Namen", Label), da wir über die Zeigervariable auf den Inhalt der Bezugsvariablen zugreifen können. Dementsprechend muss die Bezugsvariable auch nicht im Quelltext vereinbart werden, sondern kann während des Programmlaufs im Speicher eingerichtet werden. Deshalb ist die Bezugsvariable eine dynamische Variable. Natürlich kann der Zeiger bei dieser Art des Zugriffs auch während des Programmlaufs die Adresse einer anderen Bezugsvariable erhalten - man spricht auch davon, dass er auf eine andere Bezugsvariable ausgerichtet wird. Eine vergleichbare Art, auf Variablen zuzugreifen, kennen wir schon: die direkte und indirekte Adressierung in Assembler. In Assembler können wir eine Variable entweder direkt über ihr Label ansprechen, oder wir können sie über ein Register ansprechen, das ihre Speicheradresse enthält.

Deklaration von Zeigertypen und Zeigervariablen

Die Anfangsadresse eines Wertes im Speicher reicht natürlich noch nicht, um den Wert zu verwenden; es muss auch bekannt sein, wie viele Bytes der Wert umfasst und wie er zu interpretieren ist. Ein 1-Byte-Wert im Speicher kann ja z.B. als Wert einer Variablen vom Typ byte (und damit als Zahl) oder als Wert einer Variablen vom Typ char (und damit als Zahlencode, der für einen Buchstaben steht) verstanden werden. Deshalb muss für jede Zeigervariable eindeutig festgelegt sein, auf was für Werte sie verweist. Dazu definiert man im Programm bestimmte Zeigertypen: TYPE t_zeiger = ^char;

z.B. vereinbart einen Zeigertyp t_zeiger, der auf Werte vom Typ char zeigen soll.

Zeigervariablen deklariert man dann wie gewohnt bei den Variablendeklarationen, wobei man als Datentyp t_zeiger (oder irgendeinen anderen, zuvor selbst deklarierten Typennamen) angibt. Beispiel: VAR zeiger1 : t_zeiger; buchstabe : char;

Einrichten und Verwenden von Bezugsvariablen

Wie oben erwähnt, werden die Bezugsvariablen der Zeiger erst während des Programmlaufs im Speicher eingerichtet. Dazu verwendet man die Prozedur new(zeigervariable). Diese bewirkt, dass der Speicherplatz für die Bezugsvariable im Speicher reserviert und die Zeigervariable auf die Anfangsadresse dieses Speicherbereichs ausgerichtet wird. Nachdem die Bezugsvariable im Speicher eingerichtet wurde, kann man über den Zeiger auf sie zugreifen und sie wie eine normale Variable des entsprechenden Typs verwenden. Dazu schreibt man zeigervariable^, um deutlich zu machen, dass es nicht um die Zeigervariable selbst, sondern um die Bezugsvariable geht.

  1.  
    • PROGRAM Beispiel1;
    • TYPE t_zeiger = ^char;
    • VAR zeiger1 : t_zeiger; buchstabe : char;
    • BEGIN
    • new(zeiger1);
    • zeiger1^:='a';
    • buchstabe:=zeiger1^;
    • writeln(zeiger1^);
    • END.

Wie auch dieses Beispiel zeigt, kann die Bezugsvariable zeiger1^ hier wie jede andere Variable vom Typ char auch gebraucht werden, auch wenn die Schreibweise zunächst etwas ungewohnt erscheint.

Operationen mit Zeigervariablen

Natürlich kann man nicht nur mit den Bezugsvariablen, sondern auch mit den Zeigervariablen selbst arbeiten. Dabei ist vor allem von Bedeutung, dass man

  1. einem Zeiger des selben Typs die Adresse eines andere Zeigers zuweisen kann und diesen damit ebenfalls auf die gleiche Bezugsvariable ausrichten kann (zeiger2:=zeiger1;)
  2. und dass man Zeiger wie andere Variablen auch vergleichen kann, so dass man also mit zeiger2<>zeiger1 bzw. zeiger2=zeiger1 überprüfen kann, ob zwei Zeiger auf die gleiche Bezugsvariable zeigen.
  1.  
    • PROGRAM Beispiel2;
    • TYPE t_zeiger = ^char;
    • VAR zeiger1,zeiger2 : t_zeiger;
    • BEGIN
    • new(zeiger1);
    • zeiger2:=zeiger1;
    • IF zeiger1=zeiger2 THEN writeln('Beide gleich ausgerichtet.');
    • new(zeiger1);
    • IF zeiger1<>zeiger2 THEN writeln('Nicht mehr gleich - zeiger1'+' hat eine neue Bezugsvariable.');
    • END.

Besonderheiten im Umgang mit Zeigervariablen

Bei komplexeren Programmen und besonders bei Programmen, die später während der Laufzeit auch zusätzliche Zeiger einrichten (wie das möglich ist, kommt in der nächsten Zeit) ist es oft notwendig, herauszufinden, ob ein Zeiger überhaupt schon auf eine Bezugsvariable ausgerichtet wurde oder nicht. Leider enthalten Zeigervariablen aber - wie alle anderen Variablen auch - zunächst einmal irgendwelche zufälligen Daten, die im Falle des Zeigers dann fälschlicherweise als Adresse interpretiert würden. Deshalb hat man sich darauf geeinigt, allen noch nicht ausgerichteten Zeigervariablen den besonderen Wert NIL zuzuweisen, der von Pascal nicht fälschlicherweise als Adresse mißverstanden werden kann. Um zu überprüfen, ob ein bestimmter Zeiger eine Bezugsvariable hat, kann man dann einfach überprüfen, ob er gleich NIL ist.

  1.  
    • PROGRAM Beispiel3;
    • TYPE t_zeiger = ^char;
    • VAR zeiger1,zeiger2 : t_zeiger;
    • BEGIN
    • {Zeiger zu Beginn auf NIL setzen}
    • zeiger1:=NIL; zeiger2:=NIL;
    • {... weiterer Programmablauf ...}
    • IF zeiger1=NIL THEN new(zeiger1);
    • {Wenn zeiger1 noch keine Bezugsvariable hat, soll jetzt eine
    • eingerichtet werden.}
    • {...}
    • END.

Beim Programmieren mit Zeigervariablen kann außerdem ein ungewohntes Problem auftreten: Man kann die Möglichkeit verlieren, auf eine Bezugsvariable zuzugreifen, wenn man den auf sie zeigenden Zeiger neu ausrichtet, ohne die Adresse zwischenzuspeichern.

  1.  
    • PROGRAM Beispiel4;
    • TYPE t_zeiger = ^integer;
    • VAR zeiger1,zeiger2 : t_zeiger;
    • BEGIN
    • new(zeiger1); new(zeiger2);
    • zeiger1^:=10000;
    • zeiger2^:=7635;
    • zeiger2:=zeiger1;
    • {Wir können jetzt nicht mehr auf die 7635 zugreifen, da
    • beide Zeiger jetzt auf die 10000 zeigen und wir die
    • ursprüngliche Bezugsadresse von zeiger2 nicht gesichert haben.}
    • {...}
    • END.

Natürlich kann es sein, dass man eine Bezugsvariable im weiteren Programmlauf nicht mehr brauchen wird. Dann kann man den betreffenden Zeiger einfach wie oben neu ausrichten. Dies ist jedoch ungünstig, da der Speicherplatz der entsprechenden Bezugsvariable dann reserviert bleibt und bis zum Programmende nicht mehr zur Verfügung steht. Besser ist es deshalb, die Bezugsvariable zunächst mit dispose(zeigervariable); zu löschen, so dass ihr Speicherplatz freigegeben wird, und den Zeiger erst danach neu auszurichten.