In diesem Kapitel erfahren Sie etwas über das vielleicht wichtigste und vielseitigste Thema in der Programmiersprache C. Dabei handelt es sich allerdings auch um ein Thema, welches oft auf Anhieb nicht gleich verstanden wird. Aber ist einem Zeigerarithmetik erst mal klar, erscheinen die Hürden der Fortgeschrittenen Kapitel nicht mehr so hoch. C C++ C/C++ Zeiger Pointer void Arrays Zeigerarithmetik Derefernzierung call by reference Zeiger - Pointer void Arrays Zeigerarithmetik Derefernzierung call by reference Kapitel 15: Zeiger (Pointer)

15.7. Array und Zeiger            zurück  Ein Kapitel höher  zum Inhaltsverzeichnis

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. Wenn Sie sich dies merken, dürften Sie mit diesem Abschnitt keine Probleme bekommen.

Zuerst folgt ein Beispiel, wie mit Zeigern auf ein Array zugegriffen werden kann:

#include <stdio.h>

int main()
{
   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 0;
}

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:

#include <stdio.h>

int main()
{
   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 0;
}

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 15.14: Visuelle Darstellung des Zeigers und des Arrays im Speicher
Abbildung 15.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 15.15: Zeiger ptr verweist auf das erste Array-Element
Abbildung 15.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 15.16: Die Adresse des Zeigers wurde erhöht
Abbildung 15.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 15.17: Nach einer weiteren Erhöhung der Adresse des Zeigers
Abbildung 15.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:

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:

#include <stdio.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()
{
   int werte[] = { 1,2,3,5,8,13,21 };

   funktion(werte, sizeof(werte)/sizeof(int));
   return 0;
}

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 macht dem Compiler heutzutage keine Probleme mehr. Somit dürfte es keine bemerkbaren Laufzeitverluste bei der Verwendung des Indizierungsoperators geben.

15.8. Zeiger auf Strings            zurück  Ein Kapitel tiefer  Ein Kapitel tiefer  zum Inhaltsverzeichnis

Alles, was bisher zu den Zeigern mit Arrays gesagt wurde, gilt auch für Zeiger auf Strings. Häufig wird dabei irrtümlicherweise von einem Zeiger gesprochen, der auf einen String verweist. Dieses Missverständnis entsteht durch folgende Deklaration:

char *string = "Hallo Welt";

Dies ist eine Stringkonstante, auf die ein Zeiger zeigt. Genauer: Der Zeiger zeigt auf die Anfangsadresse dieser Konstante, den Buchstaben 'H'. Hierzu ein Beispiel:

#include <stdio.h>

void funktion(char *str)
{
   printf("%s\n",str);
}

int main()
{
   char *string = "Hallo Welt";
   funktion(string);
   printf("Anfangsadresse auf die *string zeigt = %p\n",*string);
   printf("Der Inhalt dieser Anfangsadresse     = %c\n",*string);
   return 0;
}

Wie diese Funktion hier werden übrigens alle ANSI C-Funktionen deklariert, die Zeichenketten verarbeiten. All diese Funktionen (printf() zum Beispiel) bekommen als Argument nur die Anfangsadresse einer Zeichenkette übergeben. Anschließend lesen diese Funktionen Zeichen für Zeichen aus, bis auf das Stringende-Zeichen '\0' getroffen wird. Wobei Ihnen bei einem genaueren Blick auffallen dürfte, dass bei vielen Funktionen die Variablen-Parameter mit dem Schlüsselwort const deklariert sind.

15.8.1 Zeiger auf konstante Objekte (Read-only-Zeiger)
Zeiger, die als Zusatz das Schlüsselwort const enthalten, sind so genannte Read-only-Zeiger. Das bedeutet, auf diese Zeiger kann nur lesend zugegriffen werden. Angenommen, die Syntax von printf() ist in der Headerdatei <stdio.h> folgendermaßen deklariert:

int printf (const char*, ...); 

Mit dieser Angabe kann aus dem Parameter der Funktion printf() nur gelesen werden. Es ist also nicht möglich diesen Inhalt irgendwie mit einem anderen Zeiger zu manipulieren. Hierzu ein Beispiel:

#include <stdio.h>

void funktion1(char *str)
{
   char *ptr;
   ptr = str+5;
   *ptr = '-';
}

int main()
{
   char string1[] = "Hallo Welt\n";

   funktion1(string1);
   printf("%s\n",string1);
   return 0;
}

Hier wird die Zeichenfolge Hallo Welt in der Funktion manipuliert. Zwischen Hallo und Welt wird in der Funktion ein Bindestrich eingefügt. Wollen Sie dies vermeiden, müssen Sie nur die Funktionsdeklaration ändern:

void funktion1(const char *str)  

Sie können jetzt zwar die einzelnen Inhalte der Zeichenkette lesen, aber nicht mehr in der Funktion ändern. Außerhalb der Funktion ist der Schreibschutz natürlich wieder aufgehoben.

15.9. Zeiger auf Zeiger und Stringtabellen            zurück  Ein Kapitel höher  zum Inhaltsverzeichnis

Zeiger auf Zeiger ist ein recht schwieriges Thema, aber es zu verstehen, lohnt sich. Hierzu die Syntax von Zeigern auf Zeiger:

datentyp **bezeichner;

Was heißt jetzt "Zeiger auf Zeiger" genau? Sie haben einen Zeiger, der auf einen Zeiger zeigt, welcher auf eine Variable zeigt, und auf diese Variable zurückgreifen kann. Im Fachjargon wird dabei von einer mehrfachen Indirektion gesprochen. Theoretisch ist es auch möglich, Zeiger auf Zeiger auf Zeiger usw. zu verwenden. In der Praxis machen allerdings solche mehrfachen Indirektionen kaum noch Sinn. Meistens verwenden Sie Zeiger auf Zeiger, also zwei Dimensionen.

Das Haupteinsatzgebiet von Zeigern auf Zeiger liegt in der dynamischen Erzeugung von mehrdimensionalen Arrays wie beispielsweise Matrizenberechnungen. Aber darauf wird in Kapitel 17, Dynamische Speicherverwaltung, eingegangen. Zuerst ein Beispiel zu diesem komplexen Thema:

#include <stdio.h>

int main()
{
   int wert = 10;
   /* ptr ist ein Zeiger auf int wert */
   int *ptr=&wert;
   /* ptr_ptr ist ein Zeiger auf den Zeiger int *ptr */
   int **ptr_ptr=&ptr;

   printf("*ptr      = %d\n",*ptr);
   printf("**ptr_ptr = %d\n", **ptr_ptr);

   /* Verändert den Wert, auf den int *ptr zeigt */
   **ptr_ptr = 100;
   printf("*ptr      = %d\n",*ptr);
   printf("**ptr_ptr = %d\n", **ptr_ptr);

   /* Verändert nochmals den Wert  */
   *ptr = 200;
   printf("*ptr      = %d\n",*ptr);
   printf("**ptr_ptr = %d\n", **ptr_ptr);
   return 0;
}

Wichtig in diesem Beispiel ist, dass Sie bei der Veränderung der Variablen den doppelten Indirektionsoperator (**) einsetzen, genauso wie bei der Deklaration des Zeigers auf einen Zeiger. Hätten Sie nämlich anstatt

**ptr_ptr = 100; 

Folgendes geschrieben

*ptr_ptr = 100;

würde der Zeiger ptr_ptr auf die Speicheradresse 100 verweisen. Und dies ist zumeist irgendwo im Nirwana des Speichers. Wie gesagt, in Kapitel 17 wird dieses Thema nochmals aufgegriffen, und es wird Ihnen dort einiges sinnvoller erscheinen.

15.9.1 Stringtabellen

Um Ihnen jetzt endgültig den Verstand zu rauben, will ich gleich noch die Stringtabellen hinzunehmen, welche den Zeigern auf Zeiger nicht unähnlich sind (aber nicht dasselbe sind).

Ein Beispiel: Folgende Stringkonstanten sollen nach Alphabet sortiert werden (ohne Verwendung der Headerdatei <string.h>):

"Zeppelin" , "Auto" , "Amerika" , "Programmieren"

Sie wissen ja noch (sollten Sie eigentlich): *ptr repräsentiert ja dieselbe Anfangsadresse wie ptr[0]. Und Gleiches gilt jetzt auch für:

**ptrptr und *ptrptr[0]

Damit haben Sie ein Array von Zeigern. Und so würde dies im Beispiel aussehen:

char *sort[]={"Zeppelin","Auto","Amerika","Programmieren"}; 

Hier haben Sie eine so genannte Stringtabelle. Wie kann jetzt auf die einzelnen Strings einer Stringtabelle zugegriffen werden? Dazu ein kleines Beispiel:

#include <stdio.h>

int main()
{
   char *sort[]={"Zeppelin","Auto","Amerika","Programmieren"};

   printf("%s\n",sort[1]);                /* Auto */
   printf("%s ",(sort[2]+2));             /* erika */
   printf("%s %s\n",(sort[0]+6),sort[2]); /* in Amerika */
   printf("%.5s\n",(sort[3]+5-2));        /* gramm */
   return 0;
}

Abbildung 15.18: Verwendung einer Stringtabelle
Abbildung 15.18: Verwendung einer Stringtabelle

Der Zugriff auf die Stringtabelle erfolgt ähnlich wie bei den mehrdimensionalen Arrays. Die erste Ausgabe

printf("%s\n",sort[1]);

gibt das Wort "Auto" auf dem Bildschirm aus. Daher kann davon ausgegangen werden, dass bei

sort[0]="Zeppelin"
sort[1]="Auto"
sort[2]="Amerika"
sort[3]="Programmieren"

mithilfe des Indizierungsoperators auf die Anfangsadressen der einzelnen Zeichenketten verwiesen wird. Mit der zweiten Anweisung

printf("%s ",(sort[2]+2)); 

wird der Name "erika" ausgegeben. Das lässt sich so erklären: sort[2] repräsentiert die Anfangsadresse von "Amerika" also "A". Danach kommt +2 hinter dem Feldindex hinzu. Der Zeiger, der ohne +2 weiterhin auch auf den Anfang von "Amerika" gezeigt hätte, zeigt jetzt auf den dritten Buchstaben des Wortes, also auf "e". Oder genauer: auf die Adresse von "e". Mit dem Formatzeichen %s wird anschließend veranlasst, dass der String von dieser Adresse ab auf dem Bildschirm ausgegeben wird. Genauso verläuft dies bei der nächsten Ausgabe. Die Schreibweise

printf("%s\n",(sort[3]+5-2));

dient nur der Demonstration, dass es so auch geht. Natürlich lässt sich das leichter lesen mit:

printf("%s\n",(sort[3]+3));

Das Programm soll nochmals anhand von Adressen demonstriert werden:

#include <stdio.h>

int main()
{
   char *sort[]={"Zeppelin","Auto","Amerika","Programmieren"};

   printf("%p = %c\n",**sort,**sort);
   printf("%p = %c\n",*sort[0],*sort[0]);
   printf("%p = %c\n",*(sort[0]+0),*(sort[0]+0));

   printf("%p = %s\n",sort[0],sort[0]);
   printf("%p = %s\n",*sort,*sort);

   printf("%p = %s\n",(sort[0]+1),(sort[0]+1));
   printf("%p = %s\n",(sort[0]+2),(sort[0]+2));

   printf("*sort = %p, **sort = %p\n",*sort,**sort);
   return 0;
}

Abbildung 15.19: Ausgabe der Adressen einer Stringtabelle
Abbildung 15.19: Ausgabe der Adressen einer Stringtabelle

Bei den ersten drei Ausgaben

printf("%p = %c\n",**sort,**sort);
printf("%p = %c\n",*sort[0],*sort[0]);
printf("%p = %c\n",*(sort[0]+0),*(sort[0]+0));

wurden immer die (Anfangs-) Adressen und Inhalte verwendet, auf die der zweite Zeiger zeigt. Was die Ausgabe auch bestätigt. Anschließend wird nur die Adresse des ersten Zeigers benutzt:

printf("%p = %s\n",sort[0],sort[0]);
printf("%p = %s\n",*sort,*sort);

Der Inhalt ist bei Benutzung von einem Zeiger natürlich derselbe wie bei der Benutzung von zwei Zeigern. Aber bei Übersetzung des Programms haben beide Zeiger eine andere Adresse. Die Ausgabe von

printf("*sort = %p, **sort = %p\n",*sort,**sort); 

bestätigt alles dies erneut. Jeder einzelne Zeiger benötigt also seinen Speicherplatz und somit auch eine eigene Adresse. Ich versuche, es noch einmal anders zu erklären:

*(*(Variable+x)+y)

Hiermit wird auf das y-te Zeichen im x-ten String gezeigt. Bei dem Programm sieht dies so aus:

*(*(sort+1)+2) 

oder auch - wie schon bekannt - so:

*((sort[1])+2)

Hiermit würde auf das 3-te Zeichen im 2-ten String verwiesen, was hierbei dem Zeichen "t" vom String "Auto" entspricht.

Jetzt soll diese Stringtabelle nach Alphabet sortiert werden. Dabei wird nicht die ganze Textzeile verlagert und unnötig hin- und herkopiert, sondern es müssen lediglich die Zeiger in die richtige Reihenfolge gebracht werden:

#include <stdio.h>
#include <string.h>  /* strcmp */

int main()
{
   char *sort[] = {"Zeppelin","Auto","Amerika","Programmieren"};
   int i,j;
   char *temp;

   for(i=0; i<4; i++)
      {
         for(j=i+1; j<4; j++)
            {
               if( (strcmp(sort[i],sort[j]) > 0) )
                  {
                     temp=sort[i];
                     sort[i]=sort[j];
                     sort[j]=temp;
                  }
            }
      }
   for(i=0;i<4;i++)
      printf("%s\n",sort[i]);
   return 0;
}

Bei diesem Sortieralgorithmus handelt es sich um "Selektion Sort". Die folgenden Zeilen sortieren die Felder mit Zeigern:

for(i=0; i<4; i++)
   {
      for(j=i+1; j<4; j++)
         {
            if((strcmp(sort[i],sort[j]) > 0))
               {
                  temp=sort[i];
                  sort[i]=sort[j];
                  sort[j]=temp;
               }
         }
   }

Zuerst wird das erste Element in der Stringtabelle mit allen anderen verglichen. So wird das kleinste Element gefunden, welches an den Anfang gestellt wird. Danach wird das zweite Element mit allen vor ihm liegenden verglichen. Dies geht so weiter bis zum letzten Element in der Stringtabelle. Mehr zu den Algorithmen finden Sie in Kapitel 25. Das Wichtigste, wie schon mehrmals erwähnt, ist, dass Zeiger für Adressen da sind und sonst nichts. Beispielsweise bedeutet

char *text[500];  

nichts anderes als ein char-Array mit 500 char-Zeigern. Genau gesagt, kann jeder dieser 500 Zeiger z.B. auf einen String (char-Array) zeigen. Beweis gefällig? Bitte sehr:

#include <stdio.h>

int main()
{
   char *text[500];
   char str1[] = "Text1";
   char str2[] = "Text2";
   char str3[] = "Text3";

   text[0] = str1;
   text[1] = str2;
   text[2] = str3;

   printf("%s %s %s\n", text[0],text[1],text[2]);
   return 0;
}

In diesem Beispiel wurde den ersten drei Zeigern jeweils die Anfangsadresse einer Stringkonstante übergeben. Mit einfachen Arrays war dies nicht ausführbar. Natürlich ist es jetzt noch nicht möglich, die Anfangsadresse eines zur Laufzeit erstellten Textes so zuzuweisen. Dazu bedarf es Kenntnissen der dynamischen Speicherverwaltung.

Als es darum ging, Strings zu sortieren, konnte mithilfe der Zeiger auf die Anfangsadresse der Strings wesentlich effektiver (schneller) sortiert werden, als wenn dies mit dem ganzen String gemacht würde. Dies daher, da ja nur Adressen auf einen String benutzt werden.

Und statt

char *sort = "Zeppelin";
char *sort2 = "Auto" ;
char *sort3 = "Amerika";
char *sort4 = "Programmieren";

zu schreiben, ist doch diese Schreibweise

char *sort[]={"Zeppelin" ,"Auto","Amerika", "Programmieren" }; 

viel effektiver und kürzer. Hier sind es vier Zeiger auf ein char-Array, die auf die Anfangsadresse eines jeden einzelnen Wortes zeigen.

Folgende Vorteile ergeben sich für den Programmierer, wenn er Stringtabellen verwendet:

Hier ein Beispiel, wie Sie Stringtabellen effektiv einsetzen können:

#include <stdio.h>
#define ASK    0
#define WORDS  1
#define START  2

#define ENGLISH 1

#ifdef GERMAN
const char *language[] = {
              "Du sprichst Deutsch?", "Einige Worte: ",
              "Feuer", "Erde", "Wasser", "Luft", "Leben", NULL
                         };
#elif ENGLISH
const char *language[] = {
                  "Do you speak english?", "Some words: ",
                 "Fire", "earth", "water", "air", "life", NULL
                         };
#else /* FRENCH */
const char *language[] = {
       "Tu parle francais?", "quelques mots: ",
       "Le feu", "terre", "de l'eau", "de l'air", "vies", NULL
                         };
#endif

int main()
{
   int i;

   printf("%s\n", language[ASK]);
   printf("%s\n",language[WORDS]);

   for(i = START; language[i] != NULL; i++)
      printf("\t%s,\n",language[i]);
   return 0;
}

Hierbei handelt es sich um ein einfaches Listing, welches mit bedingter Kompilierung ein Programm in entsprechender Sprache übersetzt. In diesem Beispiel wurde mit

#define ENGLISH 1 

die Sprache auf Englisch eingestellt. Bei Ausgabe des Programms wird dies auch bestätigt. Müssen Sie jetzt eine Version für Ihren spanischen Kollegen schreiben, müssen Sie nur nach der Stringtabelle suchen und entsprechende Einträge übersetzen und hinzufügen. So entsteht ohne allzu großen Aufwand ein internationales Programm:

#ifdef GERMAN
const char *language[] = {
              "Du sprichst Deutsch?", "Einige Worte: ",
              "Feuer", "Erde", "Wasser", "Luft", "Leben", NULL
                         };
#elif ENGLISH
const char *language[] = {
                  "Do you speak english?", "Some words: ",
                 "Fire", "earth", "water", "air", "life", NULL
                         };
#elif FRENCH
const char *language[] = {
       "Tu parle francais?", "quelques mots: ",
       "Le feu", "terre", "de l'eau", "de l'air", "vies", NULL
                         };
#else /* ESPANOL */
const char *language[] = {
       "Habla Usted espanol", "algunas palabras: ",
       "Fuego", "tierra", "agua", "aire", "vida", NULL
                         };
#endif

Mit Stringtabellen lassen sich auch komfortabel Fehlermeldungen auf dem Bildschirm ausgeben:

char *fehlermeldung[] = {
          "Mangel an Speicherplatz",
          "Speicherbereichsüberschreitung",
          "Wertbereichsüberschreitung",
          "Die Syntax scheint falsch",
          "Zugriff verweigert - keine Rechte",
          "Zugriff verweigert - falsches Passwort",
          "Unbekannter Fehler trat auf"
                        };

Zugegriffen wird auf die einzelnen Fehlermeldungen mit dem Feldindex von Nr.[0]-[6]. "Zugriff verweigert - keine Rechte" beispielsweise ist somit fehlermeldung[4].

Nach diesem Abschnitt über Zeiger auf Zeiger und den Stringtabellen kommen sicherlich jetzt die einen oder anderen Fragen auf. Vor allem wurden die Beispiele immer nur mit konstanten Werte gegeben. Um sich also wirklich effektiv und sinnvoll mit dem Thema auseinander zu setzen, müssen Sie sich noch ein wenig gedulden, bis Sie zur (ich wiederhole mich) dynamischen Speicherverwaltung gelangen (Kapitel 17).

15.10. Zeiger auf Funktionen            zum Inhaltsverzeichnis