14.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 Arrayname 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 (dies wurde ja in Kapitel 13 behandelt), weil hierbei (und nur hier) ein Array zu einem Zeiger zerfällt. Diese automatische »Vereinfachung« stellt somit für einen Anfänger eher eine »Verkomplizierung« in Bezug auf das Verständnis dar. 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 Speicher, 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 Arrayname immer als Zeiger auf das erste Array-Element angesehen wird. Hier 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, wurde bereits am Anfang erklärt.
Wenn Sie in dem eben gezeigten Beispiel unbedingt einen Adressoperator verwenden wollen, kann dies auch so geschrieben werden:
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 };
 Hier klicken, um das Bild zu Vergrößern
Abbildung 14.14
Visuelle Darstellung des Zeigers und des Arrays im Speicher
Das Array hat die Speicheradresse 0022FF60 bis 0022FF7C und eine Gesamtgröße von 32 Byte (auf 16-Bit-Systemen: 16 Byte). Ein Element hat die Größe von vier Byte, 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:
 Hier klicken, um das Bild zu Vergrößern
Abbildung 14.15
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:
 Hier klicken, um das Bild zu Vergrößern
Abbildung 14.16
Die Adresse des Zeigers wurde erhöht
Somit wäre die Ausgabe 2. Jetzt zur nächsten Anweisung:
*(ptr+3);
Hiermit wird der Wert der Adresse auf 0022FF6C erhöht. Deshalb wird auch der Wert 8 ausgegeben:
 Hier klicken, um das Bild zu Vergrößern
Abbildung 14.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 Arraynamen. Einen Array-Parameter in Funktionen können Sie auf zwei Arten deklarieren (siehe auch Kapitel 13):
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 Feldindexes in einem Zeiger macht dem Compiler heutzutage keine Probleme mehr. Somit dürfte es keine bemerkbaren Laufzeitverluste bei der Verwendung des Indizierungsoperators geben.
|