Advanced   Java   Services Pointer


Zeiger

Ein Zeiger 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.

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 wie 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. Der Pointer ipoi darf diese Adresse nur lesen. 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 darf 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

char-Pointer

Im klassischen C werden Strings (also Zeichenketten) über char-Pointer realisiert, daher gibt es hier etwas andere Regeln.


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 auf das 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* st1 = "This is a simple string";  // Stringinhalt ist konstant, Pointer nicht
puts(st1);
// st1[0] = 't';  // t statt T // Laufzeitfehler
// st1[18] = 'S';  // S statt s in string // Laufzeitfehler
st1 = "This is another string";
puts(st1);

Dieser Fall ist gleichbedeutend mit dem folgenden:

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

Falls man einen char-Pointer auf diese Weise initialisiert gibt es also keinen Unterschied zwischen char* st und const char* st. Man könnte sagen, der Compiler ergänzt const stillschweigend.

Ein Grund dafür, daß der Compiler den Inhalt von so initialisierten char-Pointern als Konstante anlegt, ist der daß identische Zeichenketten nur einmal im Speicher angelegt werden, was man durch eine Ausgabe der Adressen verifiziert. So gibt etwa die folgende Funktion zweimal dieselbe Adresse aus:

void charPointerDemo()
{
   char* st1 = "This is a simple string";  // Stringinhalt ist konstant, Pointer nicht
   const char* st2 = "This is a simple string";  // Stringinhalt ist konstant, Pointer nicht
   printf("st1 = %p\n", st1);
   printf("st2 = %p\n", st2);  // dieselbe adresse !
   printf("Arraysize = %d\n", sizeof(st1)/sizeof(char));  // 24
   printf("strlen    = %d\n", strlen(st1) );  // 23
}

Die Ausgabe kann z. Bsp. so aussehen:

address of st1 = 0040409F
address of st2 = 0040409F
Arraysize = 4
strlen    = 23

char* ist kein Arraytyp, daher ergibt sizeof(st1) nur die Größe des Pointers, also 4 (bytes). strlen liefert als Länge des Strings die Anzahl der Zeichen und da die Stringendekennung kein Zeichen ist ergibt sich 23.


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) C-Strings und char-Arrays (String veränderbar, Pointer nicht)

Es ist wichtig darauf hinzuweisen, daß in den beiden vorigen Fällen kein char-Array initialisiert wurde, char* ist kein Arraytyp. In der folgenden Funktion initialisieren wir nun einen String mit Hilfe eines char-Arrays.

void charArrayDemo()
{
   char st1[] = "This is a simple string";  // Stringinhalt ist NICHT konstant, Pointer ist konstant
   char st2[] = "This is a simple string";  // Stringinhalt ist NICHT konstant, Pointer ist konstant
   printf("st1 = %p\n", st1);
   printf("st2 = %p\n", st2);  // andere adresse !
   printf("Arraysize = %d\n", sizeof(st1)/sizeof(char));  // 24
   printf("strlen    = %d\n", strlen(st1) );  // 23
}

Das Programm macht folgende Ausgabe

st1 = 0022FF08
st2 = 0022FEF0
Arraysize = 24
strlen    = 23

Jedes Array hat also seinen eigenen Speicherbereich und wie immer sind die Arraypointer konstant. Da die den String abschließende 0 zum Array gehört ist die Stringlänge um 1 kürzer als die Arraylänge. Überdies ist im Typ char[] die Arraylänge gespeichert, im Typ char* dagegen nicht, daher ergibt sizeof(st1) die Anzahl der Bytes, die das Array verwendet.