Galileo Computing < openbook > Galileo Computing - Professionelle Bücher. Auch für Einsteiger.
Professionelle Bücher. Auch für Einsteiger.

 << zurück
C von A bis Z von Jürgen Wolf
Das umfassende Handbuch für Linux, Unix und Windows
– 2., aktualisierte und erweiterte Auflage 2006
Buch: C von A bis Z

C von A bis Z
1.116 S., mit CD, Referenzkarte, 39,90 Euro
Galileo Computing
ISBN 3-89842-643-2
gp Kapitel 14 Zeiger (Pointer)
  gp 14.1 Zeiger deklarieren
  gp 14.2 Zeiger initialisieren
    gp 14.2.1 Speichergröße von Zeigern
  gp 14.3 Zeigerarithmetik
  gp 14.4 Zeiger, die auf andere Zeiger verweisen
    gp 14.4.1 Subtraktion zweier Zeiger
  gp 14.5 Typensicherung bei der Dereferenzierung
  gp 14.6 Zeiger als Funktionsparameter (call–by–reference)
    gp 14.6.1 Zeiger als Rückgabewert
  gp 14.7 Array und Zeiger
  gp 14.8 Zeiger auf Strings
    gp 14.8.1 Zeiger auf konstante Objekte (Read-only-Zeiger)
  gp 14.9 Zeiger auf Zeiger und Stringtabellen
    gp 14.9.1 Stringtabellen
  gp 14.10 Zeiger auf Funktionen
  gp 14.11 void-Zeiger
  gp 14.12 Äquivalenz zwischen Zeigern und Arrays


Galileo Computing - Zum Seitenanfang

14.7 Array und Zeiger  toptop

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 };

Abbildung
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:

Abbildung
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:

Abbildung
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:

Abbildung
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.

 << zurück
  
  Zum Katalog
Zum Katalog: C von A bis Z
C von A bis Z
bestellen
 Ihre Meinung?
Wie hat Ihnen das <openbook> gefallen?
Ihre Meinung

 Buchtipps
Zum Katalog: Shell-Programmierung






 Shell-Programmierung


Zum Katalog: Linux-UNIX-Programmierung






 Linux-UNIX-Programmierung


Zum Katalog: C/C++






 C/C++


Zum Katalog: UML 2.0






 UML 2.0


Zum Katalog: Reguläre Ausdrücke






 Reguläre Ausdrücke


Zum Katalog: Linux






 Linux


 Shopping
Versandkostenfrei bestellen in Deutschland und Österreich
InfoInfo





Copyright © Galileo Press 2006
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das <openbook> denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.


[Galileo Computing]

Galileo Press, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, info@galileo-press.de