10.7 Zugriff auf Array-Elemente über Zeiger
Es gibt zwei Möglichkeiten, wie Sie auf ein Array-Element zugreifen können. Entweder gehen Sie den bereits bekannten Weg über den Index mit [], oder Sie verwenden hierzu die Zeiger in einer Zeiger-Versatz-Schreibweise.
Sehen Sie sich hierzu Listing 10.6 an:
00 // Kapitel10/ptr_array.c
01 #include <stdio.h>
02 #include <stdlib.h>
03 #define MAX 5
03 int main(void) {
04 int iarr[MAX] = { 12, 34, 56, 78, 90 };
05 int *iptr = NULL;
06 // iptr zeigt auf das erste Element von iarr
07 iptr = iarr;
08 printf("iarr[0] = %d\n", *iptr);
09 printf("iarr[2] = %d\n", *(iptr+2));
10 *(iptr+4) = 66; // iarr[4] = 66
11 *iptr = 99; // iarr[0] = 99
12 // alle auf einmal durchlaufen (2 Möglichkeiten)
13 int *iptr2 = iarr; // iptr2 auf den Anfang von iarr
14 for(int i=0; i < MAX; i++, iptr2++) {
15 printf("iarr[%d] = %d /", i, *(iptr+i));
16 printf(" %d\n", *iptr2); // so geht es auch
17 }
18 return EXIT_SUCCESS;
19 }
Listing 10.6 »ptr_array.c« demonstriert den Zugriff auf Arrays sowohl mit der Indexmethode als auch mit der Zeiger-Versatz-Methode.
In Zeile (07) wird der Zeiger iptr mit der Adresse des ersten Elements von iarr initialisiert. Sicherlich fragen Sie sich, warum in Zeile (07)
iptr = iarr;
anstatt
iptr = &iarr[0]
verwendet wurde. Hier gilt, dass der Name eines Arrays ohne Index automatisch eine Adresse auf das erste Element ist. Der Beweis, dass iptr auf das erste Element von iarr (genauer gesagt auf iarr[0]) verweist, wird in Zeile (08) ausgegeben.
In Zeile (09) kommt eine Zeigerarithmetik mit der Addition einer Ganzzahl zum Einsatz, bei der der Zeiger bei einer Erhöhung um die Anzahl der Bytes verschoben wird, die der Größe des durch den Zeiger referenzierten Typs entspricht. Die Zeiger-Versatz-Schreibweise *(iptr+2) ist identisch mit iarr[2]. Mit beiden Versionen wird also auf den Inhalt des dritten Elements im Array iarr zugegriffen.
Spätestens jetzt dürften Sie auch erkennen, warum Sie Zeiger richtig typisieren müssen, denn ohne das Wissen um die Speichergröße des assoziierten Typs könnte das Vorgänger- bzw. Nachfolgerelement im Array beispielsweise über die Anweisung *(iptr+2) nicht wie in Zeile (09) berechnet werden. Im letzten Beispiel wird der Typ int verwendet, daher erfolgt die Erhöhung der Adresse mit iptr+n um die Größe des Typs, auf die der Zeiger verweist. Wird also ein Zeiger vom Typ int* um 1 erhöht, verweist er auf ein int-Objekt weiter und nicht auf ein Byte weiter. Dasselbe gilt beispielsweise auch, wenn ein Zeiger vom Typ double*, der auf ein double zeigt, um 1 erhöht wird, sodass dieser Zeiger um die Anzahl der Bytes (= sizeof(double)) verschoben wird, die der Größe des durch den Zeiger referenzierten Typs entspricht.
Bitte vermischen Sie Zeiger unterschiedlicher Datentypen nicht!
Wenn Sie z. B. einen double-Zeiger auf ein int-Array verweisen lassen, was ja durchaus mit einem (hier nicht ratsamen und gewaltsamen) expliziten Cast erzwungen werden könnte, dann würde die Zeigerarithmetik nicht mehr richtig arbeiten: Es würde dann eine Erhöhung der Speicherzelle, auf die der Zeiger verweist, gemäß dem Zeiger und nicht dem Variablentyp durchgeführt. Wenn dann etwa sizeof(double) gleich 8 und ein sizeof(int) gleich 4 wäre, würde bei einer Erhöhung um 1 der Zeiger um 8 Bytes weiterlaufen.
Daher sollte es Ihnen jetzt einleuchten (wie wir auch in Abschnitt 10.6, »Zeigerarithmetik«, bereits erwähnt haben), dass Zeiger mit verschiedenen Datentypen nicht einander zugewiesen werden dürfen. Natürlich kann man mit Typecasting und void* alles in alles umwandeln, aber dann müssen Sie genau wissen, was Sie tun.
Damit der Zeiger tatsächlich auf die nächste Adresse zeigt, wurde ptr+n in Klammern gesetzt, weil Klammern eine höhere Bindungskraft haben und somit zuerst ausgewertet werden.
In der for-Schleife in den Zeilen (14) bis (17) finden Sie außerdem neben der Möglichkeit, die Adresse eines Zeigers um einem ganzzahligen Wert zu erhöhen, auch die Möglichkeit, die Adressen des Array-Zeigers mit dem Inkrementoperator (iptr2++) zu erhöhen und so durch das Array zu iterieren.
[»] Reduzieren von Zeigern
Sie können Zeiger nicht nur mit ptr+1 addieren, sondern diese ebenfalls mit ptr-1 subtrahieren. Gleiches gilt auch für den Dekrementoperator ptr--.
Um also auf ein Element eines Arrays über Zeiger zuzugreifen, haben Sie folgende Möglichkeiten:
01 int iarr[MAX] = { 11, 22, 33, 44, 55 };
02 int *iptr = iarr;
03 // Zugriff auf ein Element
04 printf("%d : %d\n", iarr[2], *(iptr+2));
05 // Zugriff auf Adressen
06 printf("%p : %p\n", &iarr[3], iptr+3);
07 // Den Array-Namen als Zeiger verwenden
08 printf("%d : %d\n", iarr[1], *(iarr+1));
09 // Zeiger indizieren
10 printf("%d : %d\n", iarr[4], iptr[4]);
Beide in Zeile (04) demonstrierten Möglichkeiten, um auf ein Element in einem Array zuzugreifen, sind gleichwertig. In Zeile (06) sehen Sie hingegen zwei gleichwertige Methoden, um auf die Adressen (!) eines Arrays zuzugreifen. In Zeile (08) können Sie sehr schön sehen, dass ein Array-Name auch als Zeiger aufgefasst werden kann, und Zeile (10) zeigt den umgekehrten Fall, in dem ein Zeiger mit der Zeiger-Index-Schreibweise indiziert wird.
Anhand dieser Beispiele können Sie erkennen, dass Arrays und Zeiger recht eng miteinander zusammenhängen. Ein Array-Name ist im Grunde ein konstanter Zeiger. Zeiger hingegen können verwendet werden, um auf die einzelnen Array-Elemente zuzugreifen.