cast eines void* Arg zu 2 verschiedenen Funktionspointern



  • Hallo c-community,

    ich habe eine Frage zur Machbarkeit eines casts.

    Ausgangssituation:

    #define    USER_FUNCTION_1    0    //  Funktion 1
    #define    USER_FUNCTION_2    1    //  Funktion 2
    typedef struct struktur{
        ...
        int (*userFunction1)(int Var1);        //  soll durch user gesetzt werden können
        char* (*userFunction2)(char* var2),    //  soll durch user gesetzt werden können
        ...
    }tStruktur, *pStruktur;
    
    /*  Setzen der beiden Funktionen  */
    unsigned int setUserFunction(pStruktur Struktur, unsigned int UserFunctionType, void* Argv){
    //  check if Struktur is initialized
    if (NULL != Struktur){
    
        /*
         *  Cast von void* Argv zu den Funktionsprototypen
         *  Fehlerhaft!!!  nur Gedankengang dargestellt als PSEUDO-QUELLCODE
         *  folgender Quellcode ist nicht fehlerfrei kompilierbar!!!
         */
    
        /*     Deklaration          */  /* Name */        /*     Cast               */    /*Argv*/
        (int   (*function1)(int var1))  Funktion_1   =    (int (*function1)(int var1))    Argv
    
        /*     Deklaration          */  /* Name */        /*     Cast               */    /*Argv*/
        (char* (*function2)(int var1))  Funktion_2   =    (int (*function1)(int var1))    Argv
    
        /*
         *  Gedankengang Ende (PSEUDO-QUELLCODE Ende)
         *  folgender Quellcode ist fehlerfrei kompilierbar!!!
         */
    
        switch(UserFunctionType){
            case   USER_FUNCTION_1:
                Struktur->userFunction1   =   Function_1;
                break;
            case   USER_FUNCTION_2:
                Struktur->userFunction2   =   Function_2;
                break;
            default:
                //  unbekannter Type
                return 0;
        }
    }
    else
        return 0;
    return 1;
    }
    

    Habe in dem Quellcode mal meinen meinen Gedankengang als Pseudo-Quellcode dargestellt. Also ich möchte sowas wie einen Funktionspointer deklarieren und diesem die Form der User-Funktionen geben, damit ich Argv danach casten kann und über den switch dann, die gewünschte Funktion setzen kann.

    Hoffe ich habe mein Problem gut darstellen können und ihr könnt mir wieder mal helfen. Es wäre auch kein Problem die Definition des Funktionspointers außerhalb zu machen und dann nur noch nen cast in der Funktion.

    MfG
    mirrowwinger



  • Nimm irgendeinen Funktionszeiger als Platzhalter.

    unsigned int setUserFunction(pStruktur Struktur, unsigned int UserFunctionType, void (*fp)());
    // oder evtl. ein wenig schoener:
    typedef void (*user_function)();
    unsigned int setUserFunction(pStruktur Struktur, unsigned int UserFunctionType, user_function f);
    

    Dann ein wenig hin und her casten.

    Was nicht geht ist Funktionszeiger mit dem Umweg über void* zu casten.



  • mirrowwinger schrieb:

    Hoffe ich habe mein Problem gut darstellen können und ihr könnt mir wieder mal helfen.

    Irgendwie nicht so gut. Zumindest kann ich keine Frage erkennen. Geht das so? Ja. Ist es sinnvoll sowas zu machen? Es klingt für mich nicht zwangsläufig nach einem guten Design, aber auch nicht zwingend nach einem schlechten. Mit etwas mehr Hintergrund was genau erreicht werden soll könnte man sinnvolle Ratschläge geben.

    Abseits vom eigentlichen Problem: Der Check* NULL != Struktur *ist imho albern. Denn weder checkst du damit ob die Struktur initialisiert ist (was dein Kommentar sagt), noch ob Speicher dafür reserviert wurde (was du mit deinem Kommentar wohl eher meintest). Letzteres kannst du nicht, zumindest nicht mit Standard-C-Mitteln, prüfen. Ergo prüfst du nur ob der Zeiger NULL ist. Selbst ein uninitialisierter Zeiger würde diese Prüfung überstehen. Lass sowas raus. Das bringt nichts.



  • Der echte Tim schrieb:

    mirrowwinger schrieb:

    Hoffe ich habe mein Problem gut darstellen können und ihr könnt mir wieder mal helfen.

    Irgendwie nicht so gut. Zumindest kann ich keine Frage erkennen. Geht das so? Ja. Ist es sinnvoll sowas zu machen? Es klingt für mich nicht zwangsläufig nach einem guten Design, aber auch nicht zwingend nach einem schlechten. Mit etwas mehr Hintergrund was genau erreicht werden soll könnte man sinnvolle Ratschläge geben.

    Abseits vom eigentlichen Problem: Der Check* NULL != Struktur *ist imho albern. Denn weder checkst du damit ob die Struktur initialisiert ist (was dein Kommentar sagt), noch ob Speicher dafür reserviert wurde (was du mit deinem Kommentar wohl eher meintest). Letzteres kannst du nicht, zumindest nicht mit Standard-C-Mitteln, prüfen. Ergo prüfst du nur ob der Zeiger NULL ist. Selbst ein uninitialisierter Zeiger würde diese Prüfung überstehen. Lass sowas raus. Das bringt nichts.

    Also erstmal danke für deinen Beitrag. Nur kurz zu deinem 2. Abschnitt:
    Leider benötige ich keine Hinweise zu meinem Programmierstil. Ja natürlich ist ein nicht initialisierter Pointer ein Problem das genauso zum Absturz des Programmes führt, wie der NULL-Pointer. Möchtest du wirklich die ca 3000 Zeilen dieser einen von ca 20 structs sehen und wirklich nachlesen warum ICH (und vieleicht auch NUR ICH) der Meinung bin, dass diese Abfrage notwendig ist? Ich habe es versucht kurz und knapp (ohne ca. 3000 Zeilen Quellcode + eventuelle Verstrickungen mit anderen structs darzustellen)darzustellen und mehr nicht, deswegen interessieren mich auch deine imho's überhaupt nicht! Falls es dir hilft, denk dir diese Abfrage einfach weg.

    Wenn du mir einen Tipp geben kannst, wie ich möglichst mit einer Funktion und einem Übergebenen Funktionspointer 2 verschiedene Funktionen setzen kann (Prinzip habe ich oben versucht dazustellen), dann bitte ich um einen Kommentar.

    @Furble Wurble
    Das heißt ich müsste einen "kleinsten gemeinsammen Nenner der beiden Funktionen finden und die beiden danach deklarieren, ala:

    void function(void* Argv)
    

    Ich werde es mal so versuchen.


  • Mod

    mirrowwinger schrieb:

    Leider benötige ich keine Hinweise zu meinem Programmierstil.

    Das ist wirklich schade, dass du die nicht benötigst. Am Ende könntest du noch was nützliches dabei lernen, also bloß vorsichtig sein!

    Das heißt ich müsste einen "kleinsten gemeinsammen Nenner der beiden Funktionen finden und die beiden danach deklarieren, ala:

    void function(void* Argv)
    

    Ich werde es mal so versuchen.

    Es braucht gar keinen gemeinsamen Nenner. Alle Funktionspointer sind miteinander kompatibel und können ineinander umgecastet werden, wie es dir gefällt. Sie sind bloß nicht (unbedingt) kompatibel zu "normalen" Zeigern auf Daten. ~Auf jedem gängigen System wird es natürlich auch mit einem void* funktionieren, aber es gibt keinen Grund, es nicht richtig zu machen und irgendeinen Funktionszeiger zu nehmen, so dass es garantiert funktioniert.~



  • SeppJ schrieb:

    mirrowwinger schrieb:

    Leider benötige ich keine Hinweise zu meinem Programmierstil.

    Das ist wirklich schade, dass du die nicht benötigst. Am Ende könntest du noch was nützliches dabei lernen, also bloß vorsichtig sein!

    Das heißt ich müsste einen "kleinsten gemeinsammen Nenner der beiden Funktionen finden und die beiden danach deklarieren, ala:

    void function(void* Argv)
    

    Sorry SeppJ & Der echte Tim natürlich möchte ich hier etwas lernen. Ist vieleicht sehr hart ausgedrückt gewesen. Das mit dem NULL werde ich nochmal prüfen. Aber etwas geärgert habe ich mich schon, da der Beitrag für mich keinen Inhalt hatte :-(. Wenn ich meine Frage schlecht ausgedrückt habe, versuche ich es nochmal mit einer Abwandlung, welches mein Bestreben vieleicht besser ausdrückt (den Quellcode habe ich jetzt erst gefunden, funktioniert aber nicht 100%).

    typedef int (*fpUserFunction1)(int var1);
    typedef char* (*fpUserFunction2)(char* var2);
    typedef struct struktur{
        ...
        int (*userFunction1)(int Var1);        //  soll durch user gesetzt werden können
        char* (*userFunction2)(char* var2),    //  soll durch user gesetzt werden können
        ...
    }tStruktur, *pStruktur;
    
    /*  Setzen der beiden Funktionen  */
    unsigned int setUserFunction(pStruktur Struktur, unsigned int UserFunctionType, void* Argv){
    //  check if Struktur is initialized
    if (NULL != Struktur){ 
        switch(UserFunctionType){
            case   USER_FUNCTION_1:
                Struktur->userFunction1   =   (fpUserFunction1)Argv;
                break;
            case   USER_FUNCTION_2:
                Struktur->userFunction2   =   (fpUserFunction2)Argv;
                break;
            default:
                //  unbekannter Type
                return 0;
        }
    }
    else
        return 0;
    return 1;
    }
    

    SeppJ schrieb:

    mirrowwinger schrieb:

    Ich werde es mal so versuchen.

    Es braucht gar keinen gemeinsamen Nenner. Alle Funktionspointer sind miteinander kompatibel und können ineinander umgecastet werden, wie es dir gefällt. Sie sind bloß nicht (unbedingt) kompatibel zu "normalen" Zeigern auf Daten. ~Auf jedem gängigen System wird es natürlich auch mit einem void* funktionieren, aber es gibt keinen Grund, es nicht richtig zu machen und irgendeinen Funktionszeiger zu nehmen, so dass es garantiert funktioniert.~

    Mit obrigen Quellcode bekomme ich aber noch die Fehlermeldung:

    error: ISO C forbids conversion of object pointer to function pointer type

    Was anscheinend mit dem Compilerzusatz -pedantic zu tun hat. Gibt es einen Weg dies zu umgehen ohne -pedantic zu löschen?



  • mirrowwinger schrieb:

    Mit obrigen Quellcode bekomme ich aber noch die Fehlermeldung:

    error: ISO C forbids conversion of object pointer to function pointer type

    Kein Wunder, nachdem Du nichts von dem was SeppJ und ich gesagt haben ("Nimm einen beliebigen Funktionszeiger, statt eines void* !") umgesetzt hast, oder? 😉

    #include <stdio.h>
    #include <assert.h>
    
    typedef void (*user_func)();
    typedef int (*user_func1)(int);
    typedef char* (*user_func2)(char*);
    
    struct gadget{
      user_func1 fp1;
      user_func2 fp2;
    };
    
    enum { UF1, UF2 };
    
    void set(struct gadget* g, int id, user_func f) {
      switch(id){
      case (UF1): g->fp1=(user_func1)f; break;
      case (UF2): g->fp2=(user_func2)f; break;
      default:
        assert(0);
      }
    }
    
    int func1(int i) { return i; }
    char* func2(char* s) { return s; }
    
    int main(void) {
      struct gadget g;
      set(&g, UF1, (user_func)func1);
      set(&g, UF2, (user_func)func2);
      printf("%s%i\n", (*g.fp2)("The Answer is "), (*g.fp1)(42));
    }
    


  • Das ist alles UB.



  • Wutz schrieb:

    Das ist alles UB.

    Weshalb?



  • Furble Wurble schrieb:

    mirrowwinger schrieb:

    Mit obrigen Quellcode bekomme ich aber noch die Fehlermeldung:

    error: ISO C forbids conversion of object pointer to function pointer type

    Kein Wunder, nachdem Du nichts von dem was SeppJ und ich gesagt haben ("Nimm einen beliebigen Funktionszeiger, statt eines void* !") umgesetzt hast, oder? 😉

    #include <stdio.h>
    #include <assert.h>
    
    typedef void (*user_func)();
    typedef int (*user_func1)(int);
    typedef char* (*user_func2)(char*);
    
    struct gadget{
      user_func1 fp1;
      user_func2 fp2;
    };
    
    enum { UF1, UF2 };
    
    void set(struct gadget* g, int id, user_func f) {
      switch(id){
      case (UF1): g->fp1=(user_func1)f; break;
      case (UF2): g->fp2=(user_func2)f; break;
      default:
        assert(0);
      }
    }
    
    int func1(int i) { return i; }
    char* func2(char* s) { return s; }
    
    int main(void) {
      struct gadget g;
      set(&g, UF1, (user_func)func1);
      set(&g, UF2, (user_func)func2);
      printf("%s%i\n", (*g.fp2)("The Answer is "), (*g.fp1)(42));
    }
    

    Gemäß Quelle soll mein dargestellter Ansatz "angeblich" (also ich habe es noch nicht getestet) ohne den Compiler-Zusatz -pedantic funktionieren.



  • SeppJ schrieb:

    Alle Funktionspointer sind miteinander kompatibel und können ineinander umgecastet werden, wie es dir gefällt. Sie sind bloß nicht (unbedingt) kompatibel zu "normalen" Zeigern auf Daten.

    Funktionszeiger können gecastet werden, das ist richtig.
    Dass alle Funktionspointer miteinander kompatibel sind, ist falsch und eine gefährliche Verkürzung der Aussage des Standards hierzu, die nämlich noch weiter geht.
    ... UB liegt vor, wenn der (einem vorigen Cast folgende) Aufruf einer Funktion über einen Zeiger vom Typ erfolgt, der inkompatibel zum aktuellen (d.h. definierten) Funktionstyp ist.
    Eine weitere Einschränkung liegt für Funktionen mit variablen Parameterlisten vor, die sind niemals zu irgendwas kompatibel, insbesondere nicht zu einem 'generischen' Funktionszeiger.
    Fazit:
    Ein Funktionscast ist immer zu vermeiden, weil ggü. einem Datenzeiger-Cast das Verhalten bei einer folgenden Dereferenzierung (bei Funktionszeigern also dem Funktionsaufruf) noch viel weniger durchschaubar ist, Fehler also schwerer reproduzierbar/analysierbar (z.B. im Debugger) sind.

    http://ideone.com/bnHmTU

    typedef void*(*PtrAllgemein)();
    
    /* statt int bla(int) : */
    void* bla(int i){printf("%d",i);return (void*)i;}
    
    /* statt char *foo(char *) : */
    void* foo(char *s){puts(s);return 0;}
    
    int main() {
    	int i;
    	enum {BLA,FOO};
    	PtrAllgemein p[] = {bla,foo};
    
    	p[BLA](2);
    	p[FOO]("hello");
    
    	/* für die Return-Typen der Funktionen sollte man dann aber nur Zeiger verwenden,
    	evtl. würden noch integer Typen gehen, die dann aber (sowohl in der Funktion als auch beim Aufruf) gecastet werden müssen: */
    
    	i = (int)p[BLA](1234);
    	printf("%d",i);
    
    	return 0;
    }
    

    Man sieht also, man kommt völlig ohne Funktionszeigercasts aus und hat trotzdem kein UB.
    --> also: liebe Kinder,
    - nie Funktionen casten!
    - keine Funktionen mit variablen Parametern über Zeiger verwenden
    - der "generische" Funktionszeiger sollte immer einen kompatiblen Rückgabetyp zu den verwendeten Funktionen besitzen, zur Not also mehrere Funktionszeigertypen verwenden, bzw. wie oben gezeigt, nur "sichere" Casts bei Objekttypen der Rückgabewerte verwenden



  • mirrowwinger schrieb:

    * folgender Quellcode ist fehlerfrei kompilierbar!!!

    lol

    Die Leute begreifen einfach nicht, dass solche Aussagen einfach nur naiv sind.
    Sie machen sich vom Compilerbauer abhängig, der je nach Gemütslage für Unsinns- bzw. UB-Code trotzdem irgendwas (aus seiner Sicht) halbwegs Brauchbares zusammenbastelt, nur dass es irgendwie weitergeht, damit der Programmierer nicht beleidigt wegen eines Compilerabbruchs ist.

    --> ein fehlerfreier Compilerdurchlauf sagt überhaupt nichts aus über die Fehlerfreiheit/Portabilität/Plattformunabhängigkeit des Codes aus (zumal der Warning-Level dabei auch noch eine Rolle spielt) und schon gar nicht über fachliche Fehler im Code (was einfacher einleuchten sollte)



  • @Wutz: Ich sehe da jetzt keinen Vorteil in Deiner Lösung. Insbesondere kann ich durchaus schreiben i = (int)p[BLA]("Hello", 0.1, bla, foo); . Da ist die Lösung mit dem casten auch nicht unsicherer.
    Ich bin mir auch nicht sicher, wie Du die Funktionen des OP, die nicht dn Rückgabetyp void* haben umbiegen willst, damit Sie in Dein Array p[] passen?

    Diese "generischen Funktionszeiger" mit all der casterei ist sicherlich nicht hübsch, aber nicht von vorneherein UB.



  • Du hast meinen Beitrag nicht verstanden.
    Dein gesamter Code ist UB.
    Meine "Lösung" für Nicht-Zeiger-Rückgabetypen habe ich gezeigt.



  • Machen wir doch mal Butter bei die Fische:

    c11 6.3.2.3/8 schrieb:

    A pointer to a function of one type may be converted to a pointer to a function of another type and back again; the result shall compare equal to the original pointer.
    If a converted pointer is used to call a function whose type is not compatible with the referenced type, the behavior is undefined.

    Das passiert aber nicht in meinem Beispiel. Woran störst Du Dich also?

    Wutz schrieb:

    Meine "Lösung" für Nicht-Zeiger-Rückgabetypen habe ich gezeigt.

    Ja stimmt. Den Rückgabetyp in einen Zeiger casten.


Log in to reply