10.11 void-Zeiger
Vielleicht stellen Sie sich an dieser Stelle (vielleicht auch etwas verwirrt) die Frage, wozu man überhaupt typenlose Zeiger braucht, die darüber hinaus noch auf ein großes Nichts zeigen. Ist dies nicht absoluter Nonsens, bei dem auch Ihre Daten in einem großen Schwarzen Loch landen? Die Antwort ist, dass ein Zeiger auf void ein nicht typisierter und damit sehr vielseitiger Zeiger ist, der kompatibel mit allen anderen Zeigertypen ist. Bei der Zuweisung kann er deshalb mit typisierten Zeigern vermischt werden, was z. B. printf() und die meisten Dateizugriffsfunktionen erst ermöglicht. Dabei darf der void-Zeiger einem typisierten Zeiger zugewiesen werden, und genauso darf dem void-Zeiger ein typisierter Zeiger zugwiesen werden. Mit einem void-Zeiger umgehen Sie also praktisch die Typüberprüfung des Compilers. In der Regel ist es ja sonst nicht erlaubt, ohne eine explizite Typkonvertierung Zeiger eines Datentyps an einen Zeiger eines anderen Datentyps zuzuweisen.
Einschränkungen von void-Zeigern
Der void-Zeiger selbst kann nicht dereferenziert werden. Sie können den void-Zeiger nicht für den Zugriff auf Objekte verwenden. Mit dem void-Zeiger sind nur folgende Operationen möglich:
-
void-Zeiger mit anderen Zeigern vergleichen
-
void-Zeiger in gültige Zeigertypen umwandeln
-
Adressen an void-Zeiger zuweisen
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. Auch viele Funktionen der Standardbibliothek haben einen void-Zeiger im Prototyp definiert. Bestes Beispiel für einen solchen Prototyp ist die Funktion malloc() zum dynamischen Reservieren von Speicher. Hier die Syntax dazu:
void* malloc( size_t size);
Der Rückgabetyp void* ist sinnvoll, weil vor der Verwendung von malloc() noch nicht bekannt ist, von welchem Zeigertyp der Speicher reserviert wird. Bei der Verwendung wird der void-Zeiger in den entsprechenden Zeigertyp, dem er zugewiesen wird, umgewandelt. Hier noch ein Beispiel:
// Speicher für 100 Elemente vom Typ float reserviert
float* fval = malloc( 100 * sizeof(float) );
// Speicher für 255 Elemente vom Typ int reserviert
int* ival = malloc( 255 * sizeof(int) );
Hat eine Funktion einen void-Zeiger als formalen Parameter, wird beim Funktionsargument der Typ in void* umgewandelt. So macht es beispielsweise die Funktion memcmp() aus der Header-Datei <string.h>:
int memcmp(const void* v1, const void* v2, size_t n);
Anhand dieser beiden Standardfunktionen können Sie auch den Vorteil der void-Zeiger erkennen. Anstatt für jeden Datentyp eine Funktion zu schreiben, wird einfach eine Version mit einem typenlosen void-Zeiger erstellt.