9. Pointer (Zeiger)
Der Umgang mit Pointern ist nicht besonders anfängerfreundlich — bei falscher Verwendung können Sie einen Programmabsturz oder Schlimmeres verursachen. Es ist also äußerste Vorsicht geboten, und Sie sollten nur dann eigene Experimente mit Pointern anstellen, wenn Sie wissen, was Sie tun.
Dieses Kapitel ist sehr kurz und soll Ihnen nur einen schnellen Einblick in den Umgang mit Pointern geben, da in späteren Artikeln grundlegende Kenntnisse über Pointern hilfreich sind.
9.1 Speicheradresse ermitteln
Variablen sind, wie wir uns erinnern, Speicherplätze, in denen Werte „aufbewahrt“ werden können. Sie besitzen einen Variablennamen, über den sie angesprochen werden können, und einen Wert, der im Speicherplatz hinterlegt wurde. Nicht zuletzt hat die Variable aber auch einen Platz im Speicherbereich des Programmes, also eine Adresse, an der die Variable gefunden werden kann. Das Anlegen einer Variablen ist eigentlich nicht ganz so trivial, wie es bisher den Eindruck hatte — zunächst muss ein ausreichend großer freier Speicherbereich reserviert werden, da ja nicht etwa zwei verschiedene Variablen an dieselbe Stelle schreiben und damit den alten Wert der anderen Variable zerstören sollen. Wenn die Variable nicht mehr benötigt wird (spätestens am Ende des Programmes, oft aber schon wesentlich früher) ist wieder eine Freigabe des Speicherbereichs nötig, damit der Speicherbedarf nicht ständig anwächst. Wenn Sie DIM
verwenden, kümmert sich zum Glück der Compiler um die korrekte Reservierung, Verwaltung und Freigabe des Speicherbereichs.
Dennoch — jede Variable besitzt eine Adresse, die den ihr zugeordneten Speicherbereich angibt. Diese Adresse lässt sich über einen Zeiger, oder auf englisch Pointer, ansprechen. Dieser Pointer wird ausgegeben, wenn vor dem Variablennamen ein @ gesetzt wird.
DIM AS INTEGER x PRINT "x befindet sich an der Speicherstelle "; @x SLEEP
Die Variable wird bei jedem Start des Programmes an einer anderen Stelle abgelegt. Bei der Adresse handelt es sich, je nach Betriebssystem, um einen 32-Bit- oder 64-Bit-Wert.
9.2 Pointer deklarieren
Es können auch Variablen direkt als Pointer deklariert werden, was bedeutet, dass sie eine Adresse speichern anstatt eines „normalen“ Wertes. Pointer-Variablen besitzen denselben Wertebereich und Speicherplatzbedarf wie eine UINTEGER
-Variable (also 32-Bit in einem 32-Bit-System und 64-Bit in einem 64-Bit-System). Insofern könnten Pointer wie UINTEGER
behandelt werden. FreeBASIC unterscheidet die beiden Datentypen jedoch intern voneinander und gibt eine Warnung aus, wenn sie im Programm nicht säuberlich voneinander getrennt werden, da ein falscher Pointerzugriff zu erheblichen Problemen führen kann.
Eine Pointer-Variable wird wie eine normale Variable deklariert, wobei auf den Variablentypen das Schlüsselwort POINTER
oder häufiger (da kürzer) PTR
folgt. POINTER
und PTR
sind in diesem Zusammenhang gleichbedeutend, und es macht keinen Unterschied, welches der beiden Schlüsselwörter Sie verwenden.
DIM AS SINGLE PTR x DIM y AS INTEGER PTR
Um auf den Wert zuzugreifen, der an einer bestimmten Adresse hinterlegt ist, wird vor die Pointer-Variable ein Stern * gestellt. Nach der Deklaration besitzt die Variable standardmäßig den Wert 0
(in diesem Fall spricht man dann von einem Nullpointer). Ein lesender oder schreibender Zugriff auf diese Adresse würde den Laufzeitfehler Segmentation fault
1 auslösen. Man kann der Pointer-Variablen jedoch zuvor die Adresse einer anderen Variablen zuweisen.
DIM AS INTEGER x = 5 ' normaler INTEGER-Wert DIM AS INTEGER PTR p ' Pointer auf einen INTEGER-Wert ' Zuweisung einer Adresse p = @x PRINT "x liegt bei der Adresse "; p; PRINT " und besitzt den Wert"; *p ' Variable x ueber ihren Pointer veraendern *p = 12 PRINT "x besitzt nun den Wert "; x SLEEP
Pointer werden vor allem dann benötigt, wenn eigene Speicherbereiche verwaltet werden sollen, etwa ein Grafikspeicher. In diesem Fall muss der Speicher jedoch selbst reserviert und auch wieder freigegeben werden. Auch bei der Übergabe von Speicherbereichen an externe Bibliotheken werden gern Pointer verwendet. Das beliebteste Austauschformat für Zeichenketten ist der ZSTRING PTR
, weil für das Speicherformat nur bekannt sein muss, dass die Zeichenkette mit einem Nullbyte endet.
9.3 Speicherverwaltung bei Arrays
Bei einem Array werden, wie in Kapitel 8 erwähnt, die Einträge im Speicher direkt hintereinander abgelegt.
DIM AS DOUBLE d(2) ' 3 DOUBLE-Eintraege DIM AS SHORT s(1, 1) ' 2x2 SHORT-Werte PRINT "Position der DOUBLE-Werte d():" PRINT @d(0), @d(1), @d(2) PRINT PRINT "Position der SHORT-Werte s():" PRINT @s(0, 0), @s(0, 1), @s(1, 0), @s(1, 1) SLEEP
Position der DOUBLE-Werte d():
3214830252 3214830260 3214830268
Position der SHORT-Werte s():
3214830212 3214830214 3214830216 3214830218
Die Ausgabe ist natürlich nur ein mögliches Beispiel. Sie sehen jedoch mehreres:
-
Die
DOUBLE
-Werte folgen im Abstand von 8 Bit aufeinander, dieSHORT
-Werte im Abstand von 2 Bit. Das entspricht der Größe des jeweiligen Datentyps. -
Bei mehrdimensionalen Arrays folgen im Speicher zuerst die Werte aufeinander, die denselben ersten Indexwert besitzen.
-
Die beiden Speicherbereiche der Arrays müssen nicht nebeneinander liegen, auch wenn die Arrays direkt nacheinander definiert wurden. Das Programm sucht sich jeweils eine passende „Lücke“ im Speicherblock, in die das Array am Stück untergebracht werden kann.
9.4 Fragen zum Kapitel
Das Verständnis von Pointern ist für Einsteiger sicher nicht so einfach, und es ist nicht schlimm, wenn im Moment noch ein paar Fragen offen bleiben. Ein paar davon habe ich hier zusammengestellt:
-
Pointer — was ist das überhaupt?
-
Wozu brauche ich sowas?
-
Wie groß ist der Pointer einer
SHORT
-Variablen? Wie groß ist der Pointer auf einenSTRING
?
Fußnoten:
1) Unter Windows war ein Segmentation fault
früher unter dem Namen „Allgemeine Schutzverletzung“ bekannt; bei aktuellen Versionen erscheint in diesem Fall eine Meldung wie „<Programmname> funktioniert nicht mehr“.