2015-06-30 4 views
5

Diese Frage ist für Testzwecke, nichts mehr.Casting Funktionszeiger auf void (*)(), dann neu zu ursprünglichen Typ

Ich versuche derzeit, Funktionszeiger mit einer anderen Anzahl von Parametern zu speichern (und diese Parameter können unterschiedliche Typen haben).

Grundsätzlich habe ich das folgende Codefragment in C++ codiert 11:

#include <functional> 
#include <iostream> 

void fct(int nb, char c, int nb2, int nb3) { 
    std::cout << nb << c << nb2 << nb3 << std::endl; 
} 

template <typename... Args> 
void call(void (*f)(), Args... args) { 
    (reinterpret_cast<void(*)(Args...)>(f))(args...); 
} 

int main(void) { 
    call(reinterpret_cast<void(*)()>(&fct), 42, 'c', 19, 94); 
} 

I wandle einen void(*)(int, char, int, int) Funktionszeiger in ein generischen void(*)() Funktionszeiger. Dann benutze ich variante Template-Parameter einfach den Funktionszeiger auf seinen ursprünglichen Typ um und rufe die Funktion mit einigen Parametern auf.

Dieser Code wird kompiliert und ausgeführt. Meistens zeigt es die guten Werte an. Dieser Code gibt mir unter Mac OS jedoch einige Valgrind-Fehler (in Bezug auf nicht initialisierte Werte) und zeigt manchmal unerwarteten Müll an.

==52187== Conditional jump or move depends on uninitialised value(s) 
==52187== at 0x1004E4C3F: _platform_memchr$VARIANT$Haswell (in /usr/lib/system/libsystem_platform.dylib) 
==52187== by 0x1002D8B96: __sfvwrite (in /usr/lib/system/libsystem_c.dylib) 
==52187== by 0x1002D90AA: fwrite (in /usr/lib/system/libsystem_c.dylib) 
==52187== by 0x100025D29: std::__1::__stdoutbuf<char>::overflow(int) (in /usr/lib/libc++.1.dylib) 
==52187== by 0x10001B91C: std::__1::basic_streambuf<char, std::__1::char_traits<char> >::xsputn(char const*, long) (in /usr/lib/libc++.1.dylib) 
==52187== by 0x10003BDB0: std::__1::ostreambuf_iterator<char, std::__1::char_traits<char> > std::__1::__pad_and_output<char, std::__1::char_traits<char> >(std::__1::ostreambuf_iterator<char, std::__1::char_traits<char> >, char const*, char const*, char const*, std::__1::ios_base&, char) (in /usr/lib/libc++.1.dylib) 
==52187== by 0x10003B9A7: std::__1::num_put<char, std::__1::ostreambuf_iterator<char, std::__1::char_traits<char> > >::do_put(std::__1::ostreambuf_iterator<char, std::__1::char_traits<char> >, std::__1::ios_base&, char, long) const (in /usr/lib/libc++.1.dylib) 
==52187== by 0x1000217A4: std::__1::basic_ostream<char, std::__1::char_traits<char> >::operator<<(int) (in /usr/lib/libc++.1.dylib) 
==52187== by 0x1000011E8: fct(int, char, int, int) (in ./a.out) 
==52187== by 0x1000013C2: void call<int, char, int, int>(void (*)(), int, char, int, int) (in ./a.out) 
==52187== by 0x100001257: main (in ./a.out) 

Ich finde das ziemlich merkwürdig, denn wenn ich die Funktion aufruft, habe ich den Funktionszeiger auf seinen ursprünglichen Typ umgestuft. Ich dachte, es wäre ähnlich, einen Datentyp auf void* zu übertragen und dann in den ursprünglichen Datentyp zu reastieren.

Was ist falsch an meinem Code? Können wir nicht Funktionszeiger auf void(*)() Zeiger werfen und dann diesen Zeiger auf die ursprüngliche Funktionszeiger-Signatur umwandeln?

Wenn nicht, gibt es andere Möglichkeiten, dies zu erreichen? Ich bin nicht interessiert an std::bind was nicht was ich will.

+0

Sie meinen Funktionszeiger? Nicht Zeiger auf Mitgliedsfunktionen? – MSalters

+1

Ich kann das nicht reproduzieren. Ist das dein tatsächlicher, wörtlicher Testfall? –

+0

Ja, das ist genau der Code, den ich ausgeführt habe (auf Macos Yosemite). Außerdem, wenn ich den letzten Parameter durch einen std :: string ersetze, habe ich Müll. –

Antwort

1

gehen auf einem Bein und erraten, was Sie es tat, um zu scheitern ...

#include <functional> 
#include <iostream> 

void fct(int nb, char c, int nb2, std::string nb3) { 
    std::cout << nb << c << nb2 << nb3 << std::endl; 
} 

template <typename... Args> 
void call(void (*f)(), Args... args) { 
    (reinterpret_cast<void(*)(Args...)>(f))(args...); 
} 

int main(void) { 
    call(reinterpret_cast<void(*)()>(&fct), 42, 'c', 19, "foobar"); 
} 

Dies wird scheitern, weil „foobar“ nie zu std::string umgewandelt wird ... wie kann der Compiler wissen, ob es geht durch Args...?

ich genau bin nicht sicher, wie std::string von einem Anrufer auf den Call-Stack geschoben wird (ein String Referenz würde als Zeiger gedrückt werden), aber ich vermute, dass es mehr als nur einen einzigen Zeiger auf char* ist. Wenn der Angerufene diesen Zeiger auf char* herausspringt und das gesamte string Mitglied erwartet, läuft es aus.

Ich denke, wenn Sie

void fct(int nb, char c, int nb2, char* nb3) 

oder

call(reinterpret_cast<void(*)()>(&fct), 42, 'c', 19, std::string("foobar")); 

dann Macht Arbeit ändern.

+1

Logisch! Ich danke Ihnen für Ihre Erklärung. Es könnte eine gute Idee sein, zu schauen, wie variadische Listen gehandhabt werden. Ich denke, dass der Valgrind-Fehler auf die instabile Implementierung von Valgrind auf macOSX zurückzuführen ist. –

1

Sie sagten, Sie sind auch an alternativen Implementierungen interessiert. Persönlich würde ich die Dinge nicht so implementieren, selbst wenn es perfekt funktioniert, sowohl Funktionszeiger als auch reinterpret_casts sind Dinge, die ich zu vermeiden versuche. Ich habe diesen Code nicht getestet, aber mein Gedanke wäre:

#include <functional> 
#include <iostream> 
#include <boost/any.hpp> 

template <typename... Args> 
void call(boost::any clbl, Args... args) { 
    auto f = boost::any_cast<std::function<void(Args...)>>(clbl); 
    f(args...); 
} 

int main(void) { 
    std::function<void(int, char, int, int)> func = fct; 
    call(boost::any(func), 42, 'c', 19, 94); 
} 

Edit: Dieser Code, kombiniert mit Ihrer Definition von fct, funktioniert einwandfrei und läuft sauber unter valgrind auf Fedora, mit clang35 zusammengestellt.

+1

Code ist übersichtlicher, aber ich denke, das verbirgt nur die reinterpret_cast (was sicherlich intern in der boost :: any_cast-Implementierung getan wird). Aber danke für dein Beispiel, ich habe bis jetzt noch nichts von boost :: any und boost :: any_cast gehört! –

+1

Es ist eigentlich ein static_cast intern. Es gibt sogar Varianten von boost :: any, die intern eine dynamische Umwandlung verwenden ... ziemlich gut, wenn man eine Ausnahme macht, statt eine segfault, wenn man versagt. –

+0

Gut zu wissen, danke! –