Die Typumwandlung von Zeigern ist eine häufige Operation in C, die es ermöglicht, allgemeine Schnittstellen zu verwenden, beispielsweise durch Arbeit mit void* oder die Implementierung universeller Datentypen. Allerdings geht die Typumwandlung von Zeigern mit bestimmten Risiken einher und unterliegt strengen Standards.
void* in C speichert eine Adresse, kennt aber den Typ des Inhalts, auf den verwiesen wird, nicht. Jeder andere Zeiger (z.B. int*, char*, struct mytype*) kann explizit (oder implizit) in void* umgewandelt werden und umgekehrt, ohne Informationsverlust (es sei denn, es kommt zu einer „Verengung“).void*) umwandeln, müssen Sie sicher sein, dass an dieser Adresse tatsächlich ein Wert des kompatiblen Typs vorhanden ist.void process(void *data) { int *arr = (int*)data; // verwenden Sie arr als ein int-Array } int main() { double x = 10; process(&x); // GEFÄHRLICH: double* wird zu int* umgewandelt, UB }
„Kann jeder Zeiger sicher zu void* und zurück ohne Verluste umgewandelt werden?“
Viele antworten „ja“ – denn der C-Standard garantiert die Umwandlung jeden Objektzeigers in void* und zurück ohne Verluste. Aber wichtig ist zu beachten: Wenn Sie einen Nicht-Objekt-Zeiger (z.B. einen Funktionszeiger) in void* umwandeln oder unterschiedliche Architekturen mischen (die Größen von Zeigern unterscheiden sich für Funktionen und Daten), erhalten Sie undefiniertes Verhalten.
void foo() {} void *p = (void*)foo; // UB! Ein Funktionszeiger kann nicht so umgewandelt werden
Geschichte
In einem Projekt mit einer plattformübergreifenden Datenerfassungssubsystem wurde ein Handler eingesetzt, der einen Zeiger auf eine Struktur in
void*umwandelte und dann zurück in den ursprünglichen Typ. Beim Wechsel zu einer Architektur, in derint*unddouble*unterschiedliche Ausrichtungen hatten, führte der Versuch,void*in den falschen Typ umzuwandeln, zu einem „bus error“ (Absturz).
Geschichte
In einem Embedded-Projekt implementierte ein Programmierer einen Ringpuffer mit einer universellen Schnittstelle auf
void*, vergaß jedoch die strengen Anforderungen an die Ausrichtung (reservierte Speicher für einchar-Array, übergab alsint*). Auf einigen Plattformen wurden die Daten für die Hardware „unverständlich“ – es traten Lese-Fehler und instabile Operationen auf.
Geschichte
In einer dynamischen Sammlung wurden Adressen als
void*gespeichert, aber es wurde vergessen, dass ein Funktionszeiger nicht invoid*umgewandelt werden kann. Der Versuch, einen Ereignishandler (Callback) über ein solches Feld zu speichern und weiterzugeben, führte auf einigen Plattformen zu Abstürzen, und es war äußerst schwierig, den Fehler zu erkennen.