Sprachen / C / Zeiger

Zeiger

Die 3 grundlegenden Zeigeroperatoren

Normale Variablen wurden in diesem C-Tutorial bereits behandelt: Bei der Deklaration einer Variablen reserviert der Compiler eine Speicherstelle mit einer eindeutigen Adresse und ordnet diese dem Variablennamen zu, so dass man mit dem Variablennamen auf den Inhalt der Speicheradresse zugreifen kann. Die Tatsache, dass hier Speicherplatz reserviert wird, wird abstrahiert bzw. liegt im Verborgenen.

Ein Zeiger (engl: Pointer) ist eine Variable dessen Name ebenfalls mit einer Speicheradresse (die zunächst uninteressant ist) assoziiert ist, aber dessen Inhalt keine Nutzdaten, sondern wiederum eine andere Speicheradresse enthält, die letztendlich auf den eigentlichen Inhalt verweist. Erst wenn der Zeiger dereferenziert wird, kann auf diesen Inhalt zugegriffen werden. Da Adressen Zahlen sind, müssen Zeiger einen numerischen Datentyp haben.

Eine Abstraktion der Hardware ist hier nicht gegeben und der Programmierer kann hier direkt auf dem Speicher arbeiten.

Zeiger/3_zeigeroperatoren
#include 

int zahl = 7;

/*Verwendung des Indirektionsoperators */
int *zeiger;

int main(void)
{
  /*Verwendung des Adresssoperators */
  zeiger = &zahl;
  printf("\nDirekter Zugriff:   wert = %d\n",zahl);

  /*Verwendung des Dereferenzierungsoperator*/
  printf("Indirekter Zugriff: wert = %d\n\n",*zeiger); 

  /*Verwendung des Adresssoperators */
  printf("Adresse von 'zahl'  = %lu\n", (unsigned long) &zahl);

  /*Der Inahlt des Zeigers wird wiedergegeben. */
  printf("Inhalt von 'zeiger' = %lu\n\n",(unsigned long) zeiger);

  return 0;
} 
      
Direkter Zugriff:   wert = 7
Indirekter Zugriff: wert = 7

Adresse von 'zahl'  = 134520852
Inhalt von 'zeiger' = 134520852

Die 3 verwendeten Zeigeroperatoren in 3_zeigeroperatoren.c ermöglichen die Deklaration (Indirektionsoperator), die Zuweisung bzw. die Ermittlung einer Adresse für den Zweck einer Zuweisung eines Zeigers (Adressoperator) und die Dereferenzierung bzw. die Wiedergabe des Speicherinhalts auf das ein Zeiger referenziert (Dereferenzierungsoperator). Anmerkung: Die Adresse unterscheidet sich von Computer zu Computer.

Modifikation

Zeiger/modifikationen
#include 

int main()
{
  int zahl = 3;
  int *z_zahl = &zahl;
        
  printf("\nVariable zahl: %d\n",zahl);
  *z_zahl = 7;
  printf("Variable zahl: %d (durch Zeiger verändert)\n",zahl);
  zahl = 1024;
  printf("Zeiger zahl: %d (durch Variable verändert)\n\n",*z_zahl);
      
  return 0;
}  
      
Variable zahl: 3
Variable zahl: 7 (durch Zeiger verändert)
Zeiger zahl: 1024 (durch Variable verändert)

Das Beispiel modifikationen.c demonstriert einen weiteren wichtigen Aspekt von Zeigern: Ein Zeiger, dessen Adresse durch einen Adressoperators einer anderen Variablen zugewiesen wurde, zeigt auf denselben Speicherbereich und Veränderungen betreffen von daher die normale Variable und den Zeiger.

Eine Übersicht mit Beispielen und Erläuterungen der Zeigeroperatoren ist hier in zu finden.

Variablentypen, Speichergröße

Zeiger können unterschiedliche Datentypen annehmen, bei denen die Zeiger von daher auf Speicherbereiche unterschiedlicher Größen verweisen. Ein int benötigt gewöhnlich 4 und ein Double 8 Byte. Bei der Deklaration wird automatisch ausreichend Speicherplatz (je nach Datentyp) alloziert und der Zeiger referenziert auf das erste Byte dieses Speicherbereiches.

Zeiger/typen_und_groessen
#include 

int main()
{
  char zeichen = 'Z';
  int integer = 7;
  double doppelt = 3.333;
        
  char *z_zeichen = &zeichen;
  int *z_integer = &integer;
  double *z_doppelt = &doppelt;
        
  printf ("\nGröße eines Char-Zeigers:    %d\n", sizeof(z_zeichen));
  printf ("Größe eines Integer-Zeigers: %d\n",   sizeof(z_integer));
  printf ("Größe eines Double-Zeigers:  %d\n\n", sizeof(z_doppelt));
  printf ("Größe des dereferenzierten Char-Zeigers:    %d\n",\
           sizeof(*z_zeichen));
  printf ("Größe des dereferenzierten Integer-Zeigers: %d\n",\
           sizeof(*z_integer));
  printf ("Größe des dereferenzierten Double-Zeigers:  %d\n\n",\
           sizeof(*z_doppelt));
    
  return 0;
} 
      
Größe eines Char-Zeigers:    4
Größe eines Integer-Zeigers: 4
Größe eines Double-Zeigers:  4

Größe des dereferenzierten Char-Zeigers:    1
Größe des dereferenzierten Integer-Zeigers: 4
Größe des dereferenzierten Double-Zeigers:  8

In typen_und_groessen.c wird dieser Sachverhalt nochmal erläutert. Da jeder Zeiger immer nur auf ein Byte zeigt, haben alle Zeiger (auch unterschiedlicher Datentypen) dieselbe Größe. Dereferenziert man jedoch diese Zeiger, lassen sich unterschiedliche Größen ermitteln, da nun mal die Werte unterschiedliche Größen haben. Der Compiler kennt diese Größen, weil die Datentypen bei der Deklaration (Bsp.: double *z_doppelt = &doppelt) angegeben wurden.

Zeiger und Arrays

Zeiger/zeiger_und_arrays
#include 

int main()
{
  double zahlen[] = {1.0,2.0,3.0,4.0,5.0,6.0,7.0};
  if(&zahlen[0] == zahlen)
    puts("\nArrayname ohne Klammern ist der \"Zeiger\".");
    
  double *zeiger = zahlen;
  int j = sizeof(zahlen)/sizeof(zahlen[0]);
  for(int i = 0; i < j;i++)
    printf("%f ",*zeiger++);            
    
  double eineAndereZahl;
  zeiger = &eineAndereZahl;
  printf("\n\n");
  return 0;
} 
      
Arrayname ohne Klammern ist der "Zeiger".
1.000000 2.000000 3.000000 4.000000 5.000000 6.000000 7.000000t

Aus dem obigen Beispiel lassen sich folgende Schlussfolgerungen ziehen:

In zeiger_und_arrays.c ist ein wenig Zeigerarithmetik vorgestellt worden: In der for-Schleife wird bei jeder Iteration der Zeiger inkrementiert, so dass bei der nächsten Iteration der nächste double-Wert bereitsteht. Der Compiler addiert der Adresse automatisch 8 Byte hinzu, da er den Datentyp bereits kennt (bei int würde er 4 Byte und bei char 1 Byte dazu addieren).

Nicht-initialisierte Zeiger verwenden

Versuch folgendes Programm zu kompilieren und auszuführen:

Zeiger/segfault
#include 

int main()
{
  int *zahl;
  *zahl = 7;
  return 0;
} 
      
Segmentation fault

Hehh? Wieso geht das denn nicht” mögen sich einige C-Neulinge fragen: „Der Zeiger wurde doch deklariert und mit dem Dereferenzierungsoperator wurde diesem ein Wert zugewiesen.” Sieh dir mal die oberen Code-Beispiele an: Erkennst du den Unterschied? Wiederholung: Eine gewöhnliche Variable besteht aus einem Variablennamen, die bei der Deklaration eine Speicheradresse zugewiesen bekommt. Diese Variable ist im eigentlichen Sinne schon initialisiert, da es den Wert enthält, der zufällig auf dem Speicher lag, das vom Compiler für diese Variable reserviert wurde. Deswegen sollte man auch sofort der neuen Variable einen Wert zuweisen.

Der Variablenname eines Zeigers ist ebenfalls mit einer Adresse assoziiert, nämlich mit der Adresse, auf der eine Adresse gespeichert ist, die auf den eigentlichen Inhalt zeigt. Deswegen muss ein Zeiger in gewisser Hinsicht zwei mal initialisiert werden (die Adresse bzw. der Zeiger und der Wert des Zeigers). Ansonsten enthält der Zeiger einen zufälligen Wert auf den wahrscheinlich nicht zugegriffen werden kann. Beim Versuch darauf zuzugreifen, wird ein Segmentation fault geworfen. *zahl = 7; initialisiert nicht den Zeiger, sondern nur auf das Gezeigte.

Zeiger/no_segfault
#include 
#include 

int main()
{
  int *zahl = (int*)malloc(sizeof(int));
  *zahl = 42;
  printf("%d\n",*zahl);
  return 0;
} 
      
43

no_segfault.c lässt sich ausführen, da der Zeiger initialisiert wurde, indem ihm mit malloc() eine gültige Adresse zugewiesen wurde.

Autor: behalx
Erstellt am: 21.01.2010
Zuletzt modifiziert: 23.04.2011
Lizenz:
Creative Commons License