#include <pronix.de>

Home

 Programmieren
C-Programmieren
+ OpenBook
+ Linuxprogrammierung
+ Gtk+
+ Win32-API
Perl
CGI

 Bücher
C von A bis Z
C M&T easy
Rezensionen

 Service
Links
Feedback
Mailingliste
Newsletter

 Sonstiges
FAQ
Impressum
 

Kapitel 15: Zeiger (Pointer)

15.10. Zeiger auf Funktionen            zurück  Ein Kapitel tiefer  zum Inhaltsverzeichnis

Mit den Zeigern können Sie auch auf Maschinencode von anderen Funktionen zeigen, welche schließlich ebenfalls eine Anfangsadresse im Speicher besitzen. Ein einfaches Beispiel dazu:

#include <stdio.h>

int main()
{
   int (*ptr)(const char*,...);
   ptr=printf;

   (*ptr)("Hallo Welt\n");
   return 0;
}

Jetzt eine Erklärung zur folgenden Schreibweise:

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

Dies ist ein Zeiger auf eine Funktion, welche einen variablen, langen String erhält und einen int-Wert zurückgibt. Die Funktion printf() zum Beispiel ist eine solche Funktion:

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

Daher bekommt der Zeiger die Adresse dieser Funktion mit folgender Anweisung:

ptr=printf;

Jetzt können Sie die Funktion printf() mit dem Zeiger aufrufen:

(*ptr)("Hallo Welt\n");

Die erste Klammerung des Zeigers ptr ist wichtig. Würden Sie diese weglassen, dann würden Sie ptr als eine Funktion deklarieren, die einen int-Zeiger zurückgibt. Sie können den (Funktions-) Zeiger auch auf eine andere Funktion zeigen lassen, mit demselben Rückgabewert und dem- bzw. denselben Argument(en). Hier das Listing von eben mit weiteren Adressierungen:

#include <stdio.h>

int main()
{
   int (*ptr)(const char*,...);
   int zahl;
   ptr=printf;
   (*ptr)("Bitte eine Zahl eingeben: ");
   ptr=scanf;
   (*ptr)("%d",&zahl);
   ptr=printf;
   (*ptr)("Die Zahl lautet %d\n",zahl);
   return 0;
}

Dieses Beispiel sollte natürlich keine Schule machen und Ihnen nur zeigen, wie Sie mit Zeigern auf Funktionen zeigen können.

Zeiger auf Funktionen können ganz nützlich sein. Sie können die Zeiger in einem Feld speichern und eine Sprungtabelle daraus machen. Damit ist gemeint, dass die einzelnen Funktionen mit einem Feldindex angesprochen werden können, ähnlich wie bei den Stringtabellen - also ein Array von Funktionen, wenn Sie so wollen. Dazu soll das Listing von eben wieder herhalten:

#include <stdio.h>
#define INPUT  0
#define OUTPUT 1

int main()
{
   int (*ptr[])(const char *, ...) = { scanf, printf };
   int zahl;

   (*ptr[OUTPUT])("Bitte eine Zahl eingeben: ");
   (*ptr[INPUT])("%d",&zahl);

   (*ptr[OUTPUT])("Die Zahl lautet %d\n",zahl);
   return 0;
}

Viel verändert hat sich hierbei nicht. Anstatt

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

wurde hier einfach noch ein Indizierungsoperator hinzugefügt. Am Ende befinden sich zwischen den geschweiften Klammern noch die einzelnen Funktionen, auf die Sie im Programm mithilfe des Indizierungsoperators und dem entsprechenden Index zurückgreifen können:

int (*ptr[])(const char *, ...) = { scanf, printf };

In diesem Beispiel zeigt (*ptr[0]) auf die Adresse der Funktion scanf(), und (*ptr[1]) zeigt auf die Funktion printf(). Im Programm wurden hierbei symbolische Konstanten verwendet, um diese beiden Funktionen besser auseinander zu halten.

Voraussetzung dafür, dass diese Zeiger auf Funktionen auch funktionieren, ist immer, dass der Rückgabewert (hier vom Typ int) und der/die Parameter der Funktion (hier (const char *,…)) übereinstimmen. Sie können hierbei nicht einfach zusätzlich z.B. die Funktion fgets() zum Einlesen von Strings anhängen:

/* falsch */
int (*ptr[])(const char *, ...) = { scanf, printf, fgets };

Die Funktion fgets() erwartet andere Argumente, hier (const char *,…).

Wenn es möglich ist, mit Zeigern auf Funktionen der Standard-Bibliothek zu zeigen, dann ist es selbstverständlich auch möglich, mit Zeigern auf selbst geschriebene Funktionen zu zeigen.

#include <stdio.h>
/* Bei Linux für math.h den Compilerflag -lm mitangeben:
   gcc -o programm programm.c -lm
 */
#include <math.h>    /* sqrt() */

int addition(int zahl)
{
   int y;
   printf("%d+>",zahl);
   scanf("%d",&y);
   fflush(stdin);
   return zahl+=y;
}

int subtraktion(int zahl)
{
   int y;
   printf("%d->",zahl);
   scanf("%d",&y);
   fflush(stdin);
   return zahl-=y;
}

int division(int zahl)
{
   int y;
   printf("%d/>",zahl);
   scanf("%d",&y);
   fflush(stdin);
   return zahl/=y;
}

int multiplikation(int zahl)
{
   int y;
   printf("%d*>",zahl);
   scanf("%d",&y);
   fflush(stdin);
   return zahl*=y;
}

int sqrtw(int zahl)
{
   double x=sqrt((double)zahl);
   printf("(sqrt)%f>",x);
   return (int)x;
}

int (*rechenfunk[]) (int) = { addition, subtraktion,
                              division, multiplikation,
                              sqrtw };

int main()
{
   char op;
   static int zahl;
   printf("no.>");
   scanf("%d",&zahl);
   do {
       printf(" op>");
       scanf("%c",&op);
       fflush(stdin);
       switch(op)
          {
             case '+': printf("%d",zahl=(*rechenfunk[0])(zahl));
                       break;
             case '-': printf("%d",zahl=(*rechenfunk[1])(zahl));
                       break;
             case '/': printf("%d",zahl=(*rechenfunk[2])(zahl));
                       break;
             case '*': printf("%d",zahl=(*rechenfunk[3])(zahl));
                       break;
             case 'q': printf("%d",zahl=(*rechenfunk[4])(zahl));
                       break;
             default : printf("op '=','+','-','/','*','q'\n");
          }
      } while(op != '=');
   printf("Gesamtergebnis=%d\n",zahl);
   return 0;
}

Abbildung 15.20: Zeiger auf selbst geschriebene Funktionen
Abbildung 15.20: Zeiger auf selbst geschriebene Funktionen

Dies Programm stellt einen kleinen Taschenrechner dar, welcher allerdings stark verbesserungswürdig ist. Es können fünf Rechenoperationen wie Addition, Subtraktion, Multiplikation, Division und die Quadratwurzel verwendet werden. Es wird so lange gerechnet, bis das =-Zeichen eingegeben wurde. Hierzu folgt ein kurzer Trocken-Durchlauf. Nachdem das Programm gestartet wurde, sollte auf dem Bildschirm Folgendes erscheinen:

no.> 

Hier geben Sie die erste Zahl ein und drücken ENTER, beispielsweise die Zahl 8. Anschließend geht es in der do while-Schleife weiter. Jetzt erscheint auf dem Bildschirm:

op>

Hier muss der Rechenoperator eingegeben werden: entweder +, -, *, / oder q. In diesem Beispiel soll es eine Addition sein, also geben Sie das +-Zeichen ein und drücken ENTER. Danach wird das Zeichen im switch-Schalter darauf geprüft, ob es sich dabei um ein gültiges Zeichen handelt. In diesem Fall wäre das:

case '+': printf("%d",zahl=(*rechenfunk[0])(zahl));
          break;

Hiermit wird die Rechenfunktion mit dem Index [0] aufgerufen. Dieser Funktion wird als Parameter der Wert der Variablen zahl übergeben. Ein Blick auf die Funktionstabelle:

int (*rechenfunk[]) (int) = {   addition, subtraktion,
                                division, multiplikation,
                                sqrtw            };

Die Rechenfunktion mit dem Index [0] ist die Funktion addition. subtraktion hat den Index [1], division den Index [2] usw.

In der Funktion addition() geben Sie dann eine Zahl ein, mit welcher der übergebene Wert addiert werden soll, beispielsweise die Zahl 2, und anschließend drücken Sie wieder ENTER:

10 op>

Jetzt kann ein weiterer Operator verwendet oder mit dem =-Zeichen das Programm beendet werden.

15.11. void-Zeiger            zurück  Ein Kapitel tiefer  Ein Kapitel höher  zum Inhaltsverzeichnis

Ein Zeiger auf void ist ein typenloser und vielseitiger Zeiger. Wenn der Datentyp des Zeigers noch nicht feststeht, wird der void-Zeiger verwendet. void-Zeiger haben den Vorteil, dass Sie diesem eine beliebige Adresse zuweisen können. Außerdem kann ein void-Zeiger durch eine explizite Typenumwandlung in jeden anderen beliebigen Datentyp umgewandelt werden. Beispielsweise:

#include <stdio.h>

int main()
{
   int a=10;
   char *string = "void-Zeiger";
   void *ptr;

   /* void-Zeiger auf Variable int a */
   ptr=(int *)&a;
   printf("ptr = %p a=%p\n",ptr,&a);
   /* void-Zeiger auf string */

   ptr=(char *)string;
   printf("ptr = %p string = %p\n",ptr,string);
   return 0;
}

Natürlich sollten Sie darauf achten, dass Sie für das Casting einen Zeiger angeben und nicht etwa einen Datentyp:

/* Richtig */
ptr=(typ *)&ptr2;

/* Falsch: typ ist kein Zeiger, sondern eine Variable */
ptr=(typ)&ptr2;

Vorwiegend findet ein void-Zeiger Anwendung in Funktionen, die mit unterschiedlichen Zeigern aufgerufen werden können. Beispielsweise ist die Funktion memcmp() in der Headerdatei string.h folgendermaßen angegeben:

int memcmp (const void*, const void*, size_t);

Somit kann diese Funktion mit unterschiedlichen Zeigertypen verwendet werden, wie das folgende Beispiel zeigt:

#include <stdio.h>
#include <string.h>

int main()
{
   char str1[]="Hallo";
   char str2[]="Hallo";
   int num1[] = { 1,2,3,4,5,6 };
   int num2[] = { 1,3,5,7,9,1 };
   int cmp;

   cmp=memcmp( (char *)str1, (char *)str2, sizeof(str1));
   if(cmp ==0)
      printf("Beide Strings sind gleich\n");
   else
      printf("Die Strings sind nicht gleich\n");

   cmp=memcmp((int *)num1,(int *)num2, sizeof(num1)/sizeof(int));
   if(cmp == 0)
      printf("Der Inhalt der beiden Zahlenarrays ist gleich\n");
   else
      printf("Die Zahlenarrays sind unterschiedlich\n");
   return 0;
}

Die Umwandlung in einen entsprechenden Zeigertyp findet mit einem einfachen Typencasting statt. Für einige ist es verwirrend, wie ein leerer Zeiger (void, dt. leer) einfach so in irgendeinen Datentyp gecastet werden kann. Das deshalb, weil Sie gelernt haben, dass int vier Bytes Speicher hat, double acht Bytes Speicher und void eben keinen. Wobei void eigentlich auch nicht ganz leer ist. Verwenden Sie damit den sizeof-Operator, erfahren Sie, dass void einen Byte an Speicher benötigt.

Aber wenn Sie sich nochmals an den Anfang des Kapitels erinnern, bei dem Sie den sizeof-Operator auf alle Typen von Zeigern verwendet haben: Alle Zeiger, egal welchen Typs, haben einen Speicherbedarf von vier Bytes (32 Bit). Mehr ist auch nicht erforderlich, um eine Speicheradresse zu speichern. Ebenso sieht es mit dem void-Zeiger aus. Dieser benötigt wie alle anderen Zeiger vier Byte an Speicherplatz.

#include <stdio.h>

int main()
{
   void *void_ptr;
   printf("%d Byte\n", sizeof(void));
   return 0;
}

Wollen Sie den Typ, auf den der void-Zeiger verweist, dereferenzieren, wird die Sache ein wenig komplizierter. Dafür benötigen Sie einen weiteren Zeiger:

#include <stdio.h>

int main()
{
   void *void_ptr;
   int wert = 10;

   void_ptr=(int *)&wert;
   *(int *)void_ptr = 100;

   printf("%d\n",wert);   /* 100 */
   return 0;
}

Da der gecastete void-Zeiger allein noch nicht dereferenziert werden kann, wird hier einfach ein weiterer Zeiger verwendet:

*(int *)void_ptr = 100; 

Jetzt denken Sie sicherlich darüber nach, welchen Vorteil eigentlich ein void-Zeiger hat? Bei dem Beispiel, in welchem die Funktion memcmp() verwendet wurde, ist der Vorteil eigentlich schon klar. Anstatt für jeden Datentyp eine eigene Funktion zu schreiben, wird einfach der void-Zeiger verwendet, und der Funktion kann es egal sein, mit welchem Datentyp sie verwendet wird. Wichtig ist dabei nur, dass Sie ein richtiges Typencasting machen und die Funktion (logischerweise) entsprechend universell geschrieben wurde. Sie können nicht einfach einer Funktion, welche mit der Funktion strcmp() einzelne Strings vergleicht, als Argument die Anfangsadresse eines int-Arrays übergeben.

15.12. Äquivalenz zwischen Zeigern und Array            zurück  Ein Kapitel höher  zum Inhaltsverzeichnis

Es wurde ja bereits erwähnt, dass Zeiger und Arrays zwar eine gewisse Ähnlichkeit in ihrer Anwendung aufweisen, aber deswegen nicht gleich sind. Dennoch wird häufig beides in einen Topf geworfen.

#include <stdio.h>

int main()
{
   int var[] = { 123,456,789};

   printf("*var=%d var[0]=%d\n",*var, var[0]);
   return 0;
}

Wenn Sie dieses Beispiel übersetzen, verstehen Sie, worauf ich hinaus will. Die Angaben von var[0] und *var repräsentieren ja dieselbe Adresse. Somit stehen Ihnen also zwei Möglichkeiten zur Verfügung. Damit Sie bei diesem Wirrwarr noch die Übersicht behalten können, folgt jetzt eine Tabelle, welche die Verwandtschaft zwischen den Zeigern und den Arrays verdeutlicht. Falls Sie wieder einmal einen Quellcode finden, bei dem Sie nicht wissen, was das nun wieder sein soll, blättern Sie einfach zu diesen Tabellen.

Es sollen folgende Werte für die erste Tabelle verwendet werden:

int n=3;
int array[5]={ 0 };  /* eindim. Array mit Platz für 5 Werte*/
int *ptr = array;    /* int-Zeiger verweist jetzt auf array[0] */

Folgendes ist jetzt gleichwertig in Bezug auf den Zugriff von Werten. Zuerst die Möglichkeiten des Zugriffs auf das erste Element:

Zeiger-Variante Array-Variante
*ptr ptr[0]
*array array[0]

Tabelle 15.1: Äquivalenz beim Zugriff auf das erste Element

Als Nächstes folgt die Möglichkeit des Zugriffs auf das n-te Element:

Zeiger-Variante Array-Variante
*(ptr+n) ptr[n]
*(array+n) array[n]

Tabelle 15.2: Äquivalenz beim Zugriff auf das n-te Element

Die nächste Tabelle zeigt alle möglichen Zugriffe auf die Anfangsadresse:

Ohne Adressoperator Mit Adressoperator
ptr &ptr[0]
array &array[0]

Tabelle 15.3: Äquivalenz beim Zugriff auf die Anfangsadresse

Jetzt folgt die Tabelle für den Zugriff auf die Speicheradresse des n-ten Elements:

Ohne Adressoperator Mit Adressoperator
ptr+n &ptr[n]
array+n &array[n]

Tabelle 15.4: Äquivalenz beim Zugriff auf die Adresse des n-ten Elements

Nun folgt noch ein Listing, welches alle Punkte nochmals demonstriert:

#include <stdio.h>

int main()
{
   int n=3;
   /* eindim. Array mit Platz für 5 Werte*/
   int array[5]={ 1,2,3,4,5 };
   /* int-Zeiger verweist jetzt auf array[0] */
   int *ptr = array;
   /* 4 Möglichkeiten, um auf das erste Element zuzugreifen */
   printf("%d " ,*ptr);
   printf("%d ",ptr[0]);
   printf("%d ",*array);
   printf("%d\n",array[0]);
   /* 4 Möglichkeiten, um auf das n-te Element zuzugreifen */
   printf("%d " ,*(ptr+n));
   printf("%d ",ptr[n]);
   printf("%d ",*(array+n));
   printf("%d\n",array[n]);
   /* 4 Möglichkeiten, um auf die Anfangsadresse zuzugreifen */
   printf("%p " ,ptr);
   printf("%p ",&ptr[0]);
   printf("%p ",array);
   printf("%p\n",&array[0]);
   /* 4 Möglichkeiten, um auf die Adresse des n-ten Elements
      zuzugreifen */
   printf("%p " ,ptr+n);
   printf("%p ",&ptr[n]);
   printf("%p ",array+n);
   printf("%p\n",&array[n]);
   return 0;
}

Weiter mit Kapitel 16: Kommandozeile            zum Inhaltsverzeichnis













 

 

© 2000 - 2003 Jürgen Wolf