Darstellen eines Funktionszeigers auf einen anderen Typ
Sagen wir mal ich habe eine Funktion akzeptiert eine void (*)(void*)
Funktion Zeiger für die Verwendung als callback:
void do_stuff(void (*callback_fp)(void*), void* callback_arg);
Nun, wenn ich eine Funktion wie diese:
void my_callback_function(struct my_struct* arg);
Kann ich dies sicher?
do_stuff((void (*)(void*)) &my_callback_function, NULL);
Habe ich mir angeschaut diese Frage und ich habe einige C-Normen, die sagen, Sie können eine Umwandlung in kompatible Funktion Zeiger', aber ich kann nicht finden, eine definition, was "kompatibel Funktionszeiger' bedeutet.
InformationsquelleAutor der Frage Mike Weller | 2009-02-18
Du musst angemeldet sein, um einen Kommentar abzugeben.
So weit wie der C-standard ist besorgt, wenn Sie die Umwandlung eine Funktion Zeiger auf eine Funktion Zeiger eines anderen Typs, und rufen Sie dann, dass es Undefiniertes Verhalten. Siehe Anhang J. 2 (informativ):
Abschnitt 6.3.2.3, Absatz 8 lautet:
So, in anderen Worten, Sie können die cast-Funktion-Zeiger auf eine andere Funktion Zeiger Typ, cast es wieder, und nennen Sie es, und die Dinge funktionieren.
Die definition von kompatibel ist etwas kompliziert. Es finden sich in Abschnitt 6.7.5.3, Absatz 15:
Die Regeln für die Bestimmung, ob zwei Datentypen kompatibel sind, sind beschrieben in Abschnitt 6.2.7, und ich will nicht diese hier zitieren, da Sie ziemlich langwierig, aber Sie können, Lesen Sie auf den draft des C99-standard (PDF).
Die entsprechende Regel ist hier der in Abschnitt 6.7.5.1, Absatz 2:
Daher, da ein
void*
ist nicht kompatibel mit einemstruct my_struct*
einen Funktionszeiger vom Typvoid (*)(void*)
ist nicht kompatibel mit einem Funktionszeiger vom Typvoid (*)(struct my_struct*)
also das casting von Funktionszeigern ist technisch nicht definiertes Verhalten.In der Praxis, obwohl, können Sie sicher Weg mit casting von Funktionszeigern in einigen Fällen. In der x86-Aufrufkonventionen die Argumente werden auf den stack geschoben, und alle Zeiger die gleiche Größe (4 bytes in x86 oder 8 Byte x86_64). Aufrufen einer Funktion Zeiger darauf zu drängen, die Argumente auf dem stack und ein Indirekter Sprung zu den Funktionszeiger Ziel, und es gibt offensichtlich keine Ahnung von Typen auf der Maschinencode-Ebene.
Dinge, die Sie definitiv nicht tun:
stdcall
Aufruf-Konvention (die die MakrosCALLBACK
PASCAL
undWINAPI
alle erweitern). Wenn du an eine Funktion Zeiger, der benutzt die standard C Aufrufkonvention (cdecl
), Schlechtigkeit führen.this
parameter, und wenn Sie die Umwandlung eine member-Funktion ist eine normale Funktion, es gibt keinethis
- Objekt zu verwenden, und wieder, viel Schlechtigkeit führen.Anderen schlechte Idee, die vielleicht manchmal funktionieren, aber auch zu undefiniertem Verhalten:
void (*)(void)
zu einemvoid*
). Funktionszeiger sind nicht unbedingt die gleiche Größe wie die regelmäßige Zeiger, da auf manchen Architekturen, die Sie möglicherweise enthalten, die zusätzliche Kontext-Informationen. Dies wird wahrscheinlich funktionieren, ok auf x86, aber denken Sie daran, dass es zu undefiniertem Verhalten.InformationsquelleAutor der Antwort Adam Rosenfield
Fragte ich über dieses genaue Problem im Hinblick auf einige code in der GLib vor kurzem. (GLib ist eine zentrale Bibliothek für das GNOME-Projekt und in C geschrieben) wurde mir gesagt, das gesamte slots ' N ' 'signals framework hängt davon ab.
Gesamten code, es gibt zahlreiche Fälle der Umwandlung von Typ (1) zu (2):
typedef int (*CompareFunc) (const void *a,
const void *b)
typedef int (*CompareDataFunc) (const void *b,
const void *b,
void *user_data)
Ist es üblich, Kette-thru mit aufrufen wie diesem:
Sehen Sie selbst, hier in
g_array_sort()
: http://git.gnome.org/browse/glib/tree/glib/garray.cDen oben genannten Antworten sind ausführlich und wahrscheinlich richtig -- wenn Sie sitzen auf der Normen-Ausschuss. Adam und Johannes gebührt Anerkennung für Ihre gut recherchierten Antworten. Jedoch, in der wildnis, finden Sie diese code funktioniert Prima. Umstritten? Ja. Bedenken Sie: GLib kompiliert/arbeiten/tests an einer großen Anzahl von Plattformen (Linux/Solaris/Windows/OS X) mit einer Vielzahl von Compilern/linkern/kernel-loader (GCC/CLang/MSVC). Standards verdammt sein, denke ich.
Ich verbrachte einige Zeit darüber nachzudenken, diese Antworten. Hier ist mein Fazit:
Denken tiefer nach dem schreiben dieser Antwort, ich wäre nicht überrascht, wenn der code für den C-Compiler verwendet diese gleichen trick. Und da die (meisten/alle?) moderne C-Compiler sind installiert, würde dies bedeuten, dass der trick ist sicher.
Eine weitere wichtige Frage für die Forschung: Kann mir jemand eine Plattform/compiler/linker/loader, wo dieser trick funktioniert nicht Arbeit? Wichtige Pluspunkte für eine. Ich Wette, es gibt einige embedded-Prozessoren/Systeme, die es nicht mögen. Jedoch für das desktop-computing (und wahrscheinlich mobile/tablet), dieser trick wohl funktioniert immer noch.
InformationsquelleAutor der Antwort kevinarpe
Den Punkt wirklich nicht, ob Sie kann. Die triviale Lösung ist
Einen guten compiler nur code generieren für my_callback_helper, wenn es wirklich nötig ist, in dem Fall wären Sie froh, es getan haben.
InformationsquelleAutor der Antwort MSalters
Haben Sie eine kompatible Funktion geben, wenn der Rückgabetyp und die Parametertypen sind kompatibel - im Grunde (es ist komplizierter in der Realität :)). Kompatibilität ist das gleiche wie "gleichen Typs" nur mehr lax, um zu ermöglichen, haben verschiedene Arten, aber immer noch in irgendeiner form zu sagen: "diese Typen sind fast die gleichen". In C89, zum Beispiel zwei Strukturen kompatibel waren, wenn Sie waren ansonsten identisch, aber nur Ihr name war anders. C99 scheinen sich verändert zu haben. Zitat aus dem c Begründung-Dokument (sehr empfehlenswert zu Lesen, btw!):
Sagte - ja streng das ist Undefiniertes Verhalten, weil Ihre do_stuff Funktion oder jemand anderes wird die Funktion aufrufen mit Funktionszeiger mit
void*
als parameter, aber Ihre Funktion hat eine inkompatible parameter. Aber dennoch erwarte ich von allen Compilern zu kompilieren und führen Sie es ohne Stöhnen. Aber Sie können tun, cleaner, indem er eine andere Funktion, die einvoid*
(und die Registrierung, die als callback-Funktion), die nur rufen Sie Ihre eigentliche Funktion dann.InformationsquelleAutor der Antwort Johannes Schaub - litb
Als C-code kompiliert Instruktion, die kümmern sich nicht überhaupt über die Zeiger-Typen, ist es Recht gut, den code zu verwenden, die Sie erwähnen. Du würdest Probleme bekommen, wenn Sie ausführen würden, do_stuff mit der callback-Funktion und Zeiger auf etwas anderes dann my_struct Struktur als argument.
Ich hoffe, ich kann deutlicher machen, indem Sie zeigen, was nicht funktionieren würde:
... oder
Grundsätzlich können Sie zum cast von Zeigern auf das, was du willst, solange die Daten weiterhin sinnvoll zur Laufzeit.
InformationsquelleAutor der Antwort che
Wenn Sie denken über die Art, wie Funktionsaufrufe funktionieren in C/C++, stoßen Sie bestimmte Elemente auf dem Stapel springen, um den neuen code Lage führen, dann pop den Stapel zurück. Wenn Ihre Funktion Zeiger beschreiben die Funktionen mit den gleichen Rückgabetyp und die gleiche Anzahl/Größe der Argumente, die Sie sollten in Ordnung sein.
So, ich denke, Sie sollten in der Lage sein, dies sicher tun.
InformationsquelleAutor der Antwort Steve Rowe
Void-Pointer sind kompatibel mit anderen Typen der Zeiger. Es ist das Rückgrat, wie malloc und die mem-Funktionen (
memcpy
memcmp
) arbeiten. In der Regel in C (statt C++)NULL
ist ein makro definiert als((void *)0)
.Blick auf 6.3.2.3 (Element 1) in C99:
InformationsquelleAutor der Antwort xslogic