News... | Hack-Acad | Downloads | Web-Projekte | System-Check | Kontakt
HACKACAD - Das kleine Pointer 1mal1 (Glossar)

Ein wesentlicher Bestandteil den C anderen Programmiersprachen voraus hat, ist die Verwendung von Zeigern (engl. Pointer). Die folgenden Abschnitte fassen Schritt für Schritt Pointervariablen, Adressoperatoren, Indirection Operator und Pointer-Arithemtik zusammen.

Zum besseren Verständniss sollte pointer.c kompiliert werden.

Eine Pointervariable ähnelt einer normalen Variable insofern, dass Sie einen Datentyp und Bezeichner hat. Als Zusatz wird ein * gesetzt um die Variable als Pointer zu deklarieren. In pointer.c zum Beispiel int *pt;. Ebenso lassen sich aber auch für andere Datentypen Zeiger erstellen, sogar void Pointer.

Eine Pointervariable hat eine festgelegte Grösse von 2 oder 4 Bytes. Bei 32 Bit System 4 Bytes, ansonsten 2 Bytes.

Wird eine Variable im Programm deklariert wird diese an eine Speicherposition geschrieben. Diese Speicherposition wird Adresse genannt.



In pointer.c wurden einige Variablen deklariert und gleichzeitig initialisiert. Man sieht nun das die erste Integer Variable i1 den Wert 2 zugewiesen bekommen hat und an der Speicherposition 1245052 abgelegt wurde. Da ein INT eine Grösse von 4 Bytes hat (bei 16Bit Rechner 2 Bytes) wurde die nächste INT Variable auf die Adresse 1245048 gelegt. Nach i1 und i2 wurde d1 mit Datentyp DOUBLE angelegt. Für diesen Datentyp sind 8 Bytes notwending, daher die Adresse 1245040. Unschwer zu erkennen ist das der Speicher (Stack) sich von den hohen Adressen zu niedrigen aufbaut. Ebenso gilt hier das LIFO Prinzip. D.h. die Variable die zuletzt angelegt wurde, wird auch zuerst wieder "abgebaut" (LAST IN - FIRST OUT).



& - Der Adressoperator

Wie gesehen liefert die erste Ausgabe in pointer.c die Adressen und Werte, der zuvor deklarierten Variablen.

Die betreffende Code Zeile lautet: printf("\nwert von i1 = %i\t\t ** Adresse = %u",i1,&i1);

Das kaufmännische Und '&' am Ende der Zeile bedeutet, das nicht der Wert sondern die Adresse der Variablen angezeigt werden soll. Daher spricht man vom Adressoperator. Nun haben wir eine Pointervariable mit int *pt; angelegt. Der Pointer hat die Besonderheit, dass er als Wert auf die Adresse einer anderen Variable zeigt.
Ein korrekte Wertzuweisung erfolgt mittels: pt = &i1;
Die nächste Ausgabe in pointer.c ergibt:



Auf den ersten Blick also das was erwartet wird. i1 bleibt unverändert, der Zeiger verweist als Wert auf die Speicheradresse von i1. Wie die Ausgabe im Quellcode aussieht kann man sich nach dem zuvor geschilderten Adressoperatorfall denken. Doch wie kommt nun der Zeiger an den Wert von i1?



* - Der Indirection Operator

Ein Pointer zeigt auf die Adresse einer anderen Variablen. Diese Variable, hier i1, selbst hat bei der Initialisierung einen Wert erhalten, nämlich 2. Damit nun der Zeiger von der i1 Adresse auf den Wert zugreifen kann, wird der sogenannte "indirection operator" verwendet.

Die entsprechende Codezeile: printf("\npt zeigt auf den Wert %i",*pt);

Der Stern (*) hat hier eine andere Bedeutung als im Vergleich zur Pointer Deklaration (int *pt;). Man darf diese zwei Arten also nicht durcheinander bringen. In der printf Anweisung sorgt *pt dafür, dass der Wert der gespeicherten Adresse auf die pt zeigt, zurück geliefert wird.

Diese Prinzip lässt sich auch in umgekehrter Reihenfolge anwenden. So kann zum Beispiel über den Zeiger der Wert von i1 geändert werden, ohne das i1 direkt angesprochen wird. Die korrekte Syntax ist: *pt = 60;. Die gleiche Ausgabe von eben zeigt nun folgendes Ergebnis:



Das heisst nun wurde über die Adresse im Wert von pt, der Wert der Integer Variable i1 von 2 auf 60 gesetzt. i1 = 60; wäre ebenso möglich. Man sieht also nun das über den Zeiger beide Variable mehr oder weniger identisch sind.



Pointer als Übergabeparameter

Ein herkömmlicher Funktionsaufbau ist zum Beispiel als void Name(int para1, ...) bekannt. In diesem Fall wird beim Funktionsaufruf eine Kopie vom ersten Übergabeparameter (int para1) an die Funktion geliefert. Das bedeutet alle Änderungen innerhalb der Funktion wirken sich nicht auf die eigentliche Integer Variable aus. Übergibt man i1 und setzt den Wert in der Funktion auf 5, so wird nach Beendigung der Funktion i1 nach wie vor auf dem ursprünglichen Wert stehen, also nicht 5. Diese Art von Funktionsaufruf nennt man CALL BY VALUE.

Mit einem Zeiger kann nun die zweite Variante namens CALL BY REFERENCE bewerkstelligt werden. Es wird also nicht mehr eine Kopie der Variable erstellt, sondern die Adresse an die Funktion weitergereicht. In der Funktion wird dann mit dem Indirection Operator der Wert manipuliert. Der Übergabeparameter wird also nicht nur lokal in der Funktion sondern auch anschliessend im aufrufenden Programmteil den neuen Wert besitzen. Um eine solche Funktion zunutzen, wird der Funktionsheader wie folgt deklariert:

void WertTausch(int *Wert1)

Der Aufruf der Funktion erfolgt in diesem Beispiel dann zum Beispiel mittels:

WertTausch(&i1);

Der Adressoperator ist notwendig damit CALL BY REFERENCE wie erwähnt die Speicheradresse liefert. Innerhalb der Funktion wird dann Wert1 mit dem * verwendet (siehe pointer.c).

ACHTUNG!!!

Bei einem Array (z.B. char sz[255];) liegt ein Sonderfall vor. Ein Array ist bereits von Natur aus ein Pointer der auf das erste Element im Array zeigt. Der Aufruf bei CALL BY REFERENZ erfolgt also ohne den Adressoperator. (siehe Beispiel in pointer.c)



Pointer - Arithmetik

Noch einmal zurück blickend auf die anfängliche i1 und i2 Deklaration. Die Speicheradressen waren 1245052 und 1245048. Der Zeiger pt hat als Wert die Adresse von i1. Was passiert nun wenn wir pt++; oder pt--; ausführen?

pt wurde mit dem Datentyp INT angelegt, ein INT belegt 4 Bytes (16bit 2 Bytes). Kurzerhand wird nun der Wert des Zeigers um 4 Bytes rauf bzw. runter gezählt. Der Wert ist bei pt--; also nicht mehr 1245052 sondern 1245048. In diesem Beispiel zeigt der Pointer nun nicht mehr auf die Adresse von i1 sondern plötzlich auf i2!

Der Beweis bringt folgende Ausgabe:
printf("\nwert von pt = %u",*pt);
pt--;
printf("\nwert von pt = %u",*pt);

Somit ist es möglich mittels einer normalen ++ oder -- Anweisung wie Sie auch bei For Schleifen verwendet werden, den Wert des Zeigers zu inkrementieren, bzw. zu verringern. Die Schrittweite ist dabei abhängig vom Datentyp des Zeigers. Wäre pt vom Typ DOUBLE so würde pt++; 8 Bytes hochzählen.