12.7 Array und Zeiger 

Um Irrtümer gleich zu vermeiden: Arrays und Zeiger sind nicht das Gleiche, auch wenn dies im Verlauf dieses Kapitels den Anschein hat. Ein Zeiger ist die Adresse einer Adresse, während ein Array-Name nur eine Adresse darstellt. Dieser Irrtum, dass Array und Zeiger dasselbe sind, beruht häufig darauf, dass Array- und Zeigerdeklarationen als formale Parameter einer Funktion austauschbar sind, weil hierbei (und nur hierbei) ein Array in einen Zeiger zerfällt. Diese automatische »Vereinfachung« erschwert einem Anfänger das Verständnis jedoch. Weiterhin verstärkt wird dieses Missverständnis, wenn ein Zeiger auf einem Speicherblock, der mit malloc() dynamisch Speicher reserviert, wie ein Array verwendet wird (da der Speicherblock auch mit [] verwendet werden kann).
Internes |
Ein Array belegt zum Programmstart automatisch einen Speicherbereich, der nicht mehr verschoben oder in der Größe verändert werden kann. Einem Zeiger hingegen muss man einen Wert zuweisen, damit dieser auch auf einen belegten Speicher zeigt. Außerdem kann der »Wert« eines Zeigers später nach Belieben einem anderen »Wert« (Speicherobjekt) zugewiesen werden. Ein Zeiger muss außerdem nicht nur auf den Anfang eines Speicherblocks zeigen. |
Zuerst folgt ein Beispiel, wie mit Zeigern auf ein Array zugegriffen werden kann:
/* arr_ptr1.c */ #include <stdio.h> #include <stdlib.h> int main(void) { int element[8]= { 1, 2, 4, 8, 16, 32, 64, 128 }; int *ptr; int i; ptr = element; printf("Der Wert, auf den *ptr zeigt, ist %d\n", *ptr); printf("Durch *ptr+1 zeigt er jetzt auf %d\n", *(ptr+1)); printf("*(ptr+3) = %d\n", *(ptr+3)); printf("\nJetzt alle zusammen : \n"); for(i=0; i<8; i++) printf("element[%d]=%d \n", i, *(ptr+i)); return EXIT_SUCCESS; }
Durch die Anweisung
ptr = element;
wird dem Zeiger ptr die Adresse des Arrays element übergeben. Dies funktioniert ohne den Adressoperator, da laut ANSI-C-Standard der Array-Name immer als Zeiger auf das erste Array-Element angesehen wird. Hier sind der Beweis und das Beispiel dazu:
/* arr_ptr2.c */ #include <stdio.h> #include <stdlib.h> int main(void) { int element[8] = { 1, 2, 4, 8, 16, 32, 64, 128 }; int i; printf("*element = %d\n", *element); printf("*(element+1) = %d\n", *(element+1)); printf("*(element+3) = %d\n", *(element+3)); printf("\nJetzt alle zusammen : \n"); for(i=0; i<8; i++) printf("*(element+%d) = %d \n", i, *(element+i)); return EXIT_SUCCESS; }
Leider sind es aber exakt solche Programmbeispiele, durch die der Eindruck entsteht, Arrays und Zeiger seien gleichwertig. Warum dies nicht so ist, habe ich bereits am Anfang erklärt.
Wenn Sie in dem eben gezeigten Beispiel unbedingt einen Adressoperator verwenden wollen, können Sie dies auch so schreiben:
ptr =& element[0]; /* identisch zu ptr=element */
Auf beide Arten wird dem Zeiger die Anfangsadresse des ersten Elements vom Array mit dem Index [0] übergeben. Der Verlauf des Programms soll jetzt genauer analysiert werden.
*(ptr+1);
Mit dieser Anweisung wird aus dem Array element der Wert 2 ausgegeben, also element[1]. Wieso dies so ist, möchte ich Ihnen wieder anhand einiger Grafiken veranschaulichen.
int *ptr; ... int element[8] = { 1, 2, 4, 8, 16, 32, 64, 128 };
Abbildung 12.14 Visuelle Darstellung des Zeigers und des Arrays im Speicher
Das Array hat die Speicheradresse 0022FF60 bis 0022FF7C und eine Gesamtgröße von 32 Bytes (auf 16-Bit-Systemen: 16 Bytes). Ein Element hat die Größe von vier Bytes, da int vier Bytes groß ist (auf 32-Bit-Rechnern). Daher erfolgt auch die Adressierung immer in Vierer-Schritten. Durch die Anweisung
ptr = element /* oder */ ptr =& element[0]
sieht es im Speicher folgendermaßen aus:
Abbildung 12.15 Der Zeiger »ptr« verweist auf das erste Array-Element.
Damit verweist der Zeiger auf das erste Element im Array (oder genauer: auf die Speicheradresse des ersten Elements). Danach wird mit
*(ptr+1);
die Adresse 0022FF60 um vier Bytes erhöht. Genauso läuft dies auch mit den Arrays intern ab, wenn der Indexzähler erhöht wird.
Damit der Zeiger tatsächlich auf die nächste Adresse zeigt, muss ptr+1 zwischen Klammern stehen, weil Klammern eine höhere Bindungskraft als der Dereferenzierungsoperator haben und somit zuerst ausgewertet werden. Sollten Sie die Klammern vergessen, würde nicht auf die nächste Adresse verwiesen, sondern auf den Wert, auf den der Zeiger ptr zeigt, und dieser wird um eins erhöht.
Jetzt zeigt der Zeiger ptr durch *(ptr+1) auf:
Abbildung 12.16 Die Adresse des Zeigers wurde erhöht.
Somit wäre die Ausgabe 2. Kommen wir jetzt zur nächsten Anweisung:
*(ptr+3);
Hiermit wird der Wert der Adresse auf 0022FF6C erhöht. Deshalb wird auch der Wert 8 ausgegeben:
Abbildung 12.17 Nach einer weiteren Erhöhung der Adresse des Zeigers
Um also auf das n-te Element eines Arrays zuzugreifen, haben Sie die folgenden Möglichkeiten:
int array[10]; // Deklaration int *pointer1, *pointer2; pointer1 = array; // pointer1 auf Anfangsadresse von array pointer2 = array + 3; // pointer2 auf 4.Element von array array[0] = 99; // array[0] pointer1[1] = 88; // array[1] *(pointer1+2) = 77; // array[2] *pointer2 = 66; // array[3]
Dasselbe gilt auch für Funktionsaufrufe von Array-Namen. Einen Array-Parameter in Funktionen können Sie auf zwei Arten deklarieren:
int funktion(int elemente[]) // Gleichwertig mit ... int funktion(int *elemente)
Also kann eine Funktion mit folgenden Argumenten aufgerufen werden:
int werte[] = { 1, 2, 3, 5, 8 }; int *pointer; pointer = werte; funktion(werte); // 1. Möglichkeit funktion(&werte[0]); // 2. Möglichkeit funktion(pointer); // 3. Möglichkeit
Natürlich ist es auch möglich, die Adresse des n-ten Elements an eine Funktion zu übergeben:
funktion(&werte[2]); // Adresse vom 3.Element an funktion
Hierzu ein kleines Beispiel:
/* arr_ptr3.c */ #include <stdio.h> #include <stdlib.h> void funktion(int *array, int n_array) { int i; for(i=0; i < n_array; i++) printf("%d ",array[i]); printf("\n"); } int main(void) { int werte[] = { 1, 2, 3, 5, 8, 13, 21 }; funktion(werte, sizeof(werte) / sizeof(int)); return EXIT_SUCCESS; }
Wie sieht es aber mit dem Laufzeitverhalten aus? Was passiert, wenn die Funktion mit Feldindex verwendet wird?
void funktion(int array[], int n_array)
Compiler optimieren den Code bei der Übersetzung in der Regel selbst. Die Umwandlung eines Feldindex in einen Zeiger macht dem Compiler heutzutage keine Probleme mehr. Somit dürfte es keine bemerkbaren Laufzeitverluste bei der Verwendung des Indizierungsoperators geben.
Ihre Meinung
Wie hat Ihnen das Openbook gefallen? Wir freuen uns immer über Ihre Rückmeldung. Schreiben Sie uns gerne Ihr Feedback als E-Mail an kommunikation@rheinwerk-verlag.de.