12.11 void-Zeiger
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 diesen eine beliebige Adresse zuweisen können. Außerdem kann ein void-Zeiger durch eine explizite Typumwandlung in jeden anderen beliebigen Datentyp umgewandelt werden. Beispielsweise:
/* void_ptr1.c */ #include <stdio.h> #include <stdlib.h> int main(void) { 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 EXIT_SUCCESS; }
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;
Zwar wurde hier im Beispiel ein Cast von void * nach datentyp * gemacht, aber dies ist in C nicht unbedingt nötig – C++ allerdings macht sehr wohl einen Unterschied und braucht einen Cast von void * nach datentyp *.
Würden Sie im Beispiel oben die Casts entfernen und das Beispiel als C++-Projekt übersetzen (was bei vielen Compilern unter MS-Windows häufig voreingestellt ist), würde der Compiler eine Warnung ausgeben, wie beispielsweise:
[Warning]: In function int main : invalid conversion from `void*' to ` int*`
Und genau diese Warnmeldung lässt viele Programmierer vermuten, dass etwas am Listing falsch sei und man den void-Pointer immer casten müsste. Sofern Sie Ihren Compiler nicht davon überzeugen können, dass Sie gern ein C-Projekt schreiben würden, sollten Sie meiner Meinung nach dem sanften Druck des Compilers nachgeben (auch wenn das Programm tut, was es tun soll und ohne Problem ausgeführt werden kann) und ihm sein Cast geben – da dies ja auch nicht unbedingt »falsch« ist. Schließlich zählt zu einem der oberen Gebote eines Programmierers, dass man niemals Warnmeldungen eines Compilers ignorieren soll.
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:
/* void_ptr2.c */ #include <stdio.h> #include <stdlib.h> #include <string.h> int main(void) { char str1[]="Hallo"; char str2[]="Hallo"; int num1[] = { 1,2,3,4,5,6 }; int num2[] = { 1,3,5,7,9,1 }; int cmp; /* Casts sind nicht unbedingt nötig. */ 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"); /* Casts sind nicht unbedingt nötig. */ 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 EXIT_SUCCESS; }
Die Umwandlung in einen entsprechenden Zeigertyp findet mit einem einfachen Type-Casting statt – was auch hier nicht unbedingt nötig gewesen wäre (siehe den Abschnitt vor dem Listing).
Für einige ist es verwirrend, wie ein leerer Zeiger (void, dt. leer) einfach so in irgendeinen Datentyp gecastet werden kann, weil sie gelernt haben, dass int vier Bytes Speicher hat, double acht Bytes Speicher und void eben keinen. Dabei ist void eigentlich auch nicht ganz leer. Wenn Sie mit void den sizeof-Operator verwenden, erfahren Sie, dass void ein Byte an Speicher benötigt.
Aber erinnern Sie sich nochmals an den Anfang des Kapitels, bei dem Sie den sizeof-Operator auf alle Typen von Zeigern verwendet haben: Da habe ich gesagt, dass alle Zeiger, egal welchen Typs, einen Speicherbedarf von vier Bytes (32 Bit) haben. 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 Speicherplatz.
/* void_ptr3.c */ #include <stdio.h> #include <stdlib.h> int main(void) { void *void_ptr; printf("%d Byte\n", sizeof(void_ptr)); return EXIT_SUCCESS; }
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:
/* void_ptr4.c */ #include <stdio.h> #include <stdlib.h> int main(void) { void *void_ptr; int wert = 10; void_ptr=(int *)&wert; *(int *)void_ptr = 100; printf("%d\n",wert); /* 100 */ return EXIT_SUCCESS; }
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 dem 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 die Funktion (logischerweise) entsprechend universell geschrieben wurde. Sie können nicht einfach einer Funktion, die mit der Funktion strcmp() einzelne Strings vergleicht, als Argument die Anfangsadresse eines int-Arrays übergeben.
Ihre Meinung
Wie hat Ihnen das Openbook gefallen? Wir freuen uns immer über Ihre Rückmeldung. Schreiben Sie uns gerne Ihr Feedback als E-Mail an kommunikation@rheinwerk-verlag.de.