Advanced  Services Pointer


Pointer

Ein Pointer ist eine Variable, die die Speicheradresse einer anderen Variablen enthält. Im folgenden wird in der ersten Zeile eine Variable zahl definiert und in der zweiten Zeile eine Variable poi. poi ist ein Pointer (erkennbar am Stern *) und erhält die Adresse der Variablen zahl, erkennbar am Adreßoperator &. In dieser Situation sagt man "poi zeigt auf zahl".

int zahl = 42;
int *poi = &zahl;  // (1) Deklaration mit Initialisierung


Die zwei Bedeutungen des Sterns

Im einleitenden Beispiel wird die Variable poi definiert und sofort mit der Adresse von zahl initialisiert. Auf der Ebene der Deklaration/Definition kennzeichnet der Stern eine Pointervariable. Initialisiert man den Pointer in einem Statement darf der Stern nicht mehr verwendet werden, weil er dann eine andere Bedeutung hat.

int zahl = 42;
int *poi ;     // Deklaration bzw. Definition

poi = &zahl;  // (2) Statement, Pointervariable ohne Stern

(1) und (2) machen exakt dasselbe. Sie initialisieren die Variable poi mit der Adresse von zahl.

Wird der Stern vor einer Pointervariablen in einem Statement gesetzt, so bedeutet dies einen Zugriff auf die Variable, in diesem Fall die Variable zahl über die im Pointer gespeicherte Adresse von zahl. Man sagt, der Pointer wird dereferenziert.

printf("%d", *poi);  // ergibt 42  // Dereferenzierung

Auch ein schreibender Zugriff auf zahl über poi ist möglich:

*poi = 43;
printf("%d", zahl);  // ergibt 43

Pointer sind typisiert

Pointer bekommen beim Deklarieren einen Typ. Es ist der Typ der Variable auf die sie zeigen. Eine Ausnahme stellt der void-Pointer dar. void-Pointer werden im übernächsten Abschnitt vorgestellt.

int *ipoi, zahl, zahl2, *ipoi2;

Will man Pointer und normale Variablen in einer Zeile deklarieren, so geschieht das wie im obigen Codeschnipsel. Das Schema gilt für jeden beliebigen Typ.

double *dpoi, dpoi2, dou, dou2;

const und Pointer

Mit dem Schlüsselwort const wird angezeigt, daß etwas konstant ist. Aber was? Sobald man Pointer verwendet, kann es in Verbindung mit const leicht zu Verwechslungen kommen.

Ohne Pointer ist die Sache sehr einfach. Im folgenden führt das zweite Statement zu einem Fehler, da zahl durch const zu einer Readonlyvariablen wird. Das gilt natürlich für jeden Typ.

const int zahl = 17;
zahl = 18; // error:: assignment of read-only variable

Die Reihenfolge von const und int ist dabei egal. Das folgende ist äquivalent:

int const zahl = 17;
zahl = 18; // error:: assignment of read-only variable

Die erste Schreibweise wird in der Regel bevorzugt. Jetzt betrachten wir const im Zusammenhang mit Pointern. Hier kann sich const auf den Pointer beziehen oder auf die Variable, auf die der Pointer zeigt, je nachdem wo das Schlüsselwort const steht. der Vollständigkeit halber beginnen wir mit einem Pointer ohne const.


1)  Normaler Pointer  [int *poi]

Hier ist weder der Pointer konstant noch die Variable auf die er zeigt.

int zahl = 17;
int* ipoi = &zahl;
printf("%d", *ipoi);  // 17
*ipoi = 18; // OK
zahl = 18;  // OK
int zahl2 = 19;
ipoi = &zahl2;
*ipoi = 42; // OK

2)  Readonly-Pointer  [const int *poi]

Hier steht const vor dem Stern *.

int zahl = 17;
const int* ipoi = &zahl;
printf("%d", *ipoi);  // 17
//*ipoi = 18; // error: assignment of read-only location
zahl = 18;  // OK
int zahl2 = 19;
ipoi = &zahl2;
//*ipoi = 42; // error: assignment of read-only location

Hier bezieht sich const auf das, worauf der Pointer zeigt. Ein so definierter Pointer kann die Variable auf die er zeigt nur auslesen, aber nicht verändern. Man kann den Wert aber über die Variable zahl ändern, denn diese ist ja nicht konstant. Ebenso darf der Pointer auf eine andere Adresse zeigen. Aber diese Adresse kann dann auch nur gelesen werden. ipoi ist also ein readonly-Pointer. Ob man const int *poi oder int const *poi schreibt ist egal. Ob der Stern * am Anfang von poi klebt oder am Ende von int bzw. const spielt ebenfalls keine Rolle (const int* poi bzw. int const* poi).


3)  Konstanter Pointer  [int *const poi]

Nun wollen wird einen Pointer, der konstant auf eine Adresse zeigt, aber auf diese Adresse Lese- und Schreibzugriff hat. Hier steht const nach dem Stern *.

int zahl = 17;
int zahl2 = 42;
int* const ipoi = &zahl;  // ipoi zeigt konstant auf zahl
*ipoi = 18;
//ipoi = &zahl2; // error: assignment of read-only variable

Steht const nach dem Pointerstern bezieht es sich auf den Pointer selbst und macht diesen zu einer konstanten. Der Pointer hält hier konstant die Adresse der Variablen zahl. Eine erneute Zuweisung führt zu einem Fehler.


4)  Konstanter readonly-Pointer  [const int *const poi]

Man kann const auch zweimal verwenden, also vor und nach dem Stern *. Dann ist sowohl der Pointer konstant als auch der Wert worauf er zeigt.

int zahl = 17;
// Wert konstant und Pointer konstant
const int* const ipoi = &zahl;
//*ipoi = 69; // assignment of read-only location
//ipoi = &zahl; // assignment of read-only variable

Zusammenfassung

Rechts-Links-Leseregel

Mit dieser Regel erhält man die Möglichkeit alle drei Fälle mit einer Regel zu erfassen. Man beginnt bei der Pointervariablen und liest dann nach links weiter. Den Stern übersetzt man mit Pointer.

const int* ipoi = &zahl;

ipoi ist ein Pointer auf ein int das konstant ist.

int *const ipoi = &zahl;

ipoi ist ein konstanter Pointer auf ein int.

const int *const ipoi = &zahl;

ipoi ist ein konstanter Pointer auf ein int, das konstant ist.


void-Pointer [void*]

Ein void-Pointer ist ein Pointer, der keinem Typ zugeordet ist. So liefert etwea die Funktion malloc() Speicher der noch keinem Typ zugeordnet ist und der dann einen bestimmten Typ erhält. Im folgenden Codeschnipsel wird der Speicher zuerst auf int typisiert, anschließend dann auf char.

#include <cstdlib>

void voidpointer()
{
   void* memory = malloc(2000);  // 2000 bytes

   int *intpoi = (int*)memory;
   int *const intarr = (int*)memory;

   char *charpoi = (char*)memory;
   char *const chararr = (char*)memory;
}

Statt dem klassischen Cast kann man hier auch static_cast einsetzen.

#include <cstdlib>

void voidpointer2()
{
   void* memory = malloc(2000);

   int *intpoi = static_cast<int*>(memory);
   int *const intarr = static_cast<int*const>(memory);

   char *charpoi = static_cast<char*>(memory);
   char *const chararr = static_cast<char*const>(memory);
}

Pointer auf ein Array  [ int arr[] ]

Ein Pointer auf ein Array (erkennbar an den eckigen Klammern) wird vom Compiler immer als konstanter Pointer angelegt, er hat damit etwa für int den Typ int *const

void arraypointer()
{
   int arr1[17] = {0};
   // Array der Größe 17, alle Elemente auf 0 gesetzt

   int arr2[17] = {1, 2, 3};
   // 17 Elemente,
   // Array der Größe 17, die ersten Elemente wie in der Klammer angegeben, die
   // restlichen Elemente auf 0 gesetzt

}

char-Pointer

Im klassischen C werden Strings über char-Pointer realisiert, daher gibt es hier etwas andere Regeln. Ein char-Pointer kann auf eine einzige char-Variable zeigen oder auf einen C-String. Im letzten Fall zeigt er auf den Anfang einer Zeichenkette deren letztes Element den Inhalt 0 hat (nullterminierter String).


1)  char-Pointer zeigt auf eine char-Variable

In diesem Fall gelten genau die oben erwähnten Regeln. Man kann das als Übung durchspielen.

Im folgenden zeigt der char-Pointer immer auf einen C-String.


2)  char-Pointer zeigt auf einen C-String

Dies ist der eigentliche Sonderfall. Hier wird der char-Pointer mit einem C-String initialisiert. Die Zeichen liegen hintereinander im Speicher und nach dem letzten Zeichen wird vom Compiler eine binäre 0 als Stringendezeichen eingetragen. Der Pointer selbst zeigt auf den Anfang des Speicherbereichs, also hier im Beispiel auf T. Darüber hinaus, und das ist das besondere, wird der String als Konstante angelegt. Der Pointer selbst ist aber nicht konstant und kann auch für andere Strings verwendet werden.

char* st = "This is a simple string";  // Stringinhalt ist konstant, Pointer nicht
puts(s);
// st[17] = ' ';  // Laufzeitfehler
// st[19] = 'h';  // Laufzeitfehler
st = "This is another string";
puts(s);

Genauer gesagt, der Compiler legt in diesem Fall folgendes an:

const char* cs = "This is a simple string";  // Stringinhalt ist konstant, Pointer nicht
puts(cs);
cs = "This is another string";
puts(cs);

In der ersten Variante wurde also implizit von const char* auf char* gecastet.


3)  Konstanter readonly-char-Pointer  [char *const poi]

Das const nach dem Pointerstern müssen wir selbst schreiben.

char* const ccs = "This is a simple string";  // Stringinhalt ist konstant, Pointer auch
puts(ccs);
// ccs[0] = 'A';   // Laufzeitfehler
//ccs = "This is another string"; // assignment of read-only variable

4)  String veränderbar, Pointer nicht

Der letzte Fall. Ein veränderbarer String wird über ein klassisches Array realisiert. Die Arrayklammern, die nach dem Variablennamen stehen müssen, können leer bleiben. Der Compiler ermittelt die Arraygröße automatisch. In diesem Fall wird der Pointer als Konstante angelegt.

char str[] = "This is a simple string";   // Stringinhalt ist veränderbar, Pointer ist konstant
puts(str);
str[17] = ' ';
str[19] = 'h';
puts(str);  // This is a simple  thing
// str = "foo";   // incompatible types