Programmierkurs: Delphi: Pascal: Zeiger

Aus Wikibooks

Zeiger[Bearbeiten]

Was sind Zeiger?[Bearbeiten]

Ein Zeiger bzw. Pointer ist eine Variable, die auf einen Speicherbereich des Computers verweist (genauer: in einem Zeiger kann die Adresse eines bestimmten Speicherbereichs des Computers gespeichert werden). Zum besseren Verständnis hier ein kleines Beispiel:

Nehmen wir einmal an, wir haben einen Schrank mit 12 verschiedenen Fächern. Jedes Fach ist mit einer Nummer gekennzeichnet, dazu hat er in jedem Fach einen Gegenstand.

Im Sinne von Zeiger in einer Programmiersprache ist der Schrank der Speicherbereich des Computers, welcher dem Programm zur Verfügung gestellt wird. Jedes einzelne Fach des Schrankes repräsentiert eine Adresse auf diesem Speicher. Die Nummer des Faches entspräche der Speicheradresse, der Inhalt des Faches dem Wert des Objektes, auf das der Zeiger verweist. Auf jedem Speicher kann nun auch eine beliebige Bitreihenfolge abgespeichert werden.

Im nebenstehenden Beispiel ist a ein Zeiger auf ein Zeichen (Datentyp char), wie man ihn sich im handlichen Schrankformat vorstellen kann.

Wozu dienen Zeiger?[Bearbeiten]

Es gibt mehrere Gründe, warum man Zeiger benötigt. Zum einen sind hinter den Kulissen von Pascal sehr viele Zeiger versteckt, die große dynamische Speicherblöcke benötigen. Beispielsweise ist ein String ein Zeiger, wie auch eine Klasse nur ein einfacher Zeiger ist.

In vielen Fällen sind aber auch Zeiger dazu da, um die strikte Typisierung von Pascal zu umgehen. Dazu benötigt man einen untypisierten Zeiger, der nur den Speicherblock enthält, ohne Information dazu, was sich auf dem Speicherblock befindet.

Weiterhin kann man Zeiger dazu verwenden, um Speicher zu sparen. Dabei fordert man vom System immer nur genau soviel Speicherplatz an, wie man gerade für seine Daten benötigt. Das ist vor allem in den (zugegebenermaßen seltenen) Fällen sinnvoll, wenn man bei der Entwicklung eines Programms noch nicht weiß, welcher Art diese Daten sind und damit natürlich auch nicht weiß, wie groß die Datenmenge sein wird. Hierfür wird sich jedoch meistens eine elegantere Lösung finden. Man kann durch Zeiger auch Speicher sparen, wenn man ihn immer nur dann anfordert, wenn man ihn benötigt. Ein nicht zugewiesener Zeiger erfordert nämlich immer nur 4 Byte! Ein großes Record kann dagegen auch völlig unbenutzt mehrere KByte belegen.

Anwendung[Bearbeiten]
Deklaration[Bearbeiten]

Es gibt zwei Arten, wie man einen Zeiger deklarieren kann. Eine typisierte und eine untypisierte Variante.

var
  Zeiger1: ^Integer;  // typisiert
  Zeiger2: Pointer;   // untypisiert


Die typisierte Variante erwartet an der Speicheradresse immer einen bestimmten Datentyp, im obigen Fall Integer. Ein untypisierter Zeiger kann alle möglichen Datentypen aus der Speicheradresse auslesen, zur weiteren Verarbeitung muss jedoch eine explizite Typumwandlung durchgeführt werden.

Speicheradresse auslesen[Bearbeiten]

Im Code kann man nun entweder die Adresse oder den Inhalt des Speicherblockes auslesen. Das Auslesen der Adresse des Speicherblockes funktioniert bei beiden der oben genannten Varianten gleich.

begin
  Zeiger2 := @Zeiger1;
  Zeiger2 := Addr(Zeiger1);
end.


Wie Sie sehen, gibt es für das Auslesen der Adresse zwei Varianten. Entweder nutzt man den @-Operator oder die Funktion Addr(), wobei der @-Operator schneller ist. Hinter einem @-Operator kann jede beliebige Variable stehen, aber nur einem untypisierten Zeiger kann jede Adresse jedes Speicherblocks zugeordnert werden, ohne auf den Typ zu achten.

Inhalt auslesen[Bearbeiten]

Hier gibt es einen Unterschied zwischen typisierten und untypisierten Zeiger.

var
  i, j: Integer;
  p1: ^Integer;
  p2: Pointer;
begin
  i := 1;

  { typisiert }

  p1 := @i;       // dem Zeiger wird die Adresse der Integer-Variable übergeben
  p1^ := p1^ + 1; // hier wird der Wert um eins erhöht
  j := p1^;       // typisiert: der Variable j wird 2 übergeben

  { untypisiert }

  p2 := @i;       // analog oben
  Integer(p2^) := i + 1;
  j := Integer(p2^);
end.


Bei einem untypisierten Zeiger muss immer der Typ angegeben werden, welcher aus dem Speicher ausgelesen werden soll. Dies geschieht durch die so genannte Typumwandlung: Typ(Zeiger).

Neuen Speicher anfordern und freigeben[Bearbeiten]

In den obigen Beispielen wurde bereits vorhandener Speicher an den Zeiger übergeben und diese Daten verändert. Für gewöhnlich möchte man jedoch neue Daten im Arbeitsspeicher ablegen. Hierzu muss man sich explizit Speicher anfordern und diesen nach der Verwendung des Zeigers wieder freigeben.

Bei typisierten Zeigern erfolgt die Anforderung des Speichers durch die Anweisung New(var P: Pointer) und die Freigabe mittels Dispose(var P: Pointer). Die Größe des erforderlichen Speichers wird dabei anhand des Datentyps, auf den der Zeiger verweist, automatisch bestimmt.

Achtung: Auch wenn beide Routinen untypisierte Zeiger annehmen, funktioniert dies nicht! Delphi kompiliert zwar das Programm fehlerfrei, bei der Ausführung wird es dann jedoch abstürzen. Das liegt daran, dass untypisierte Zeiger keine festgelegte Größe haben. Pointer fungiert hier sozusagen nur als Basistyp für alle Zeigervariablen. Um auch untypisierten Zeigern Speicher zuzuweisen und ihn wieder freizugeben, müssen Sie die (sonst als veraltet geltenden) Routinen GetMem und FreeMem verwenden. GetMem erwartet als zweiten Parameter eine Größenangabe in Byte. Bei FreeMem können Sie die Größenangabe weglassen, ansonsten muss diese mit dem Wert bei der Zuweisung des Speichers übereinstimmen.

var
  p1: ^Integer;
  p2: Pointer;

begin
  New(p1);
  p1^ := 1024;
  Dispose(p1);

  GetMem(p2, 4);          // Integer ist 32 Bit, also 4 Byte groß
  Integer(p2^) := 2048;
  FreeMem(p2);            // oder: FreeMem(p2, 4);
end.


In Pascal gibt es Zahlreiche Typen, deren Größe nicht immer dokumentiert ist oder nachgeschlagen werden muss. Um zu ermitteln wie viel Speicher für den nichttypisierte Zeiger p2 reserviert werden soll bietet sich die Standardfunktion SizeOf() an. Diese Funktion arbeitet mit allen Variablen und Typenbezeichnern. Ein Beispiel:

var
  p2 : Pointer;

begin
  GetMem(p2, SizeOf(Integer)); // Statt Integer kann hier jeder andere Var/Typ stehen         
  Integer(p2^) := 2048; // Zuweisungen sind daraufhin anzupassen 
  FreeMem(p2);           
end.


Siehe hierzu auch Schnelleinstieg: Pointer

Quelle: http://docwiki.embarcadero.com/RADStudio/XE2/de/Datentypen,_Variablen_und_Konstanten


Pascal: Varianten Inhaltsverzeichnis Pascal: Variablen und Konstanten