void-pointer -- welcher cast?



  • Es ist häufig so, dass C Librarys Callback Funktionen mit einem Parameter (Typ void) anbieten, um eigene Daten in der Funktion verfügbar zu machen.

    Das ganze ist also vollkommen in Ordnung und hat mit schlechtem Design rein gar nichts zu tun. Ganz im Gegenteil.



  • @hustbaer

    ich weiß halt gerne ob sich beim cast die bits ändern oder nicht.
    Und daß wenn möglich beim Lesen des Codes, so deutlich wie es geht.

    Tatsächlich geht hier technisch sowohl static_cast als auch reinterpret_cast.

    Wenn ein einfaches Reinterpretieren ohne Wandlung reicht,
    dann darf man das auch gerne hinschreiben.

    Wobei ich mir auch Fälle denken kann,
    z.B. bei Code Änderungen, die mit static_cast auffallen würden,
    aber mit reinterpret_cast nicht.

    Was soll's

    Gruß Frank


  • Mod

    hustbaer schrieb:

    @SeppJ:
    Ja, ja, ja! 🤡
    Das ist ein *USER-DATA* Pointer, den kann er zeigen lassen auf was er will.

    Aber es ist nicht genug Platz dort, weil der vector selbst nur ein paar Byte hat! Man müsste

    cell->set_user_pointer(  new std::vector<PointHistory<dim> >[ (sizeof(<PointHistory<dim>) * quadrature_formula.size()) / sizeof (std::vector<PointHistory<dim> >)  ]  );
    

    machen, damit die Menge an Speicher da ist, die der Threadersteller haben will. Und selbst dann geht dies extrem schief, denn wenn man hinterher ein delete auf diesen Pointer macht, dann muss man ihn dafür in einen vector<PointHistory<dim> >* casten. Dies ruft aber den Destructor von Vector auf, welcher dann versucht völlig unsinnige Sachen zu machen, weil die Verwaltungsdaten mit was anderem überschrieben wurden. Das gibt eine totale Katastrophe! Das musst du doch sehen!



  • Hallo SeppJ

    Du verstehts doch eigentlich alles in diesem Zusammenhang.

    Die Library bietet 4 Bytes User Data an.
    Dort kann man also einen void* speichern,
    einen Zeiger auf irgendwas so zusagen.
    Später kann man sich diese 4 Bytes, wieder holen und interpretieren.

    Wenn ich also einen Zeiger auf einen vector in die 4 bytes rein tue,
    muss ich das beim Rausholen auch als Zeiger auf einen Vector interpretieren

    Klar zeigt das dann 'nur' auf die Verwaltungsstruktur des vectors und klar liegen
    die eigentlichen Daten nochmals woanders.

    Vieleicht hat es dir geholfen,
    wenn nicht, vieleicht hilft es morgen ...

    Übrigends ist dieser void* für generische Programmierung erforderlich.
    Alternativ könnte man für soetwas boost:any einsetzten,
    geht in diesem Fall aber nicht...

    Gruß Frank


  • Mod

    Ich weiß wohl was du meinst, aber guck mal auf den ersten Post, der threadersteller will garantiert nicht, dass der void* auf einen vector zeigt, sondern auf quadrature_formula.size() Instanzen von PointHistory<dim>. Vermutlich weil die Bibliothek ein Array von PointHistory<dim>[quadrature_formula.size()] erwartet. Und das geht eben nicht, wenn man auf einen vector zeigt.



  • So wie ich das sehe hat er folgendes vor:

    void* user_ptr = 0;
    
    void foo()
    {
        std::vector<int>* vec = static_cast<std::vector<int>*>(user_ptr);
        for (std::vector<int>::const_iterator i = vec->begin(); i != vec->end(); ++i)
            std::cout << *i << std::endl;
        delete vec;
    }
    
    int main()
    {
        int tmp[] = { 1, 2, 3, 4, 5 };
        user_ptr = new std::vector<int>(tmp, tmp + sizeof(tmp) / sizeof(int));
        foo();
    }
    

    Das funktioniert sogar wunderbar!



  • Okay, um hier weiteren Spekulationen einen Riegel vorzuschieben
    und da ich das Problem nicht genau erfasse, noch ein paar Zusatzinformationen.

    Die Bibliothek sollte nix mit user_pointer veranstalten,
    es ist wie hustbaer geschrieben hat...

    Die Library kümmert sich nicht darum was da drinnen steht, die bietet den Platz einfach nur an, damit der User bestimmte eigene Datenstrukturen an die Library-Objekte anhängen kann.

    um es genauer zu beschreiben, soll es mir Daten über bestimmte Schleifenzyklen
    transportieren.

    PointHistory ist dabei ein struct, in dem wieder eine Klasse Tensor<dim>
    eingebettet ist, das habe ich so aus dem Tutorial 1zu1 übernommen.

    Vielleicht klärt das schon ein paar Mißverständnisse...



  • Frank Erdorf schrieb:

    @hustbaer

    ich weiß halt gerne ob sich beim cast die bits ändern oder nicht.
    Und daß wenn möglich beim Lesen des Codes, so deutlich wie es geht.

    Tatsächlich geht hier technisch sowohl static_cast als auch reinterpret_cast.

    Wenn ein einfaches Reinterpretieren ohne Wandlung reicht,
    dann darf man das auch gerne hinschreiben.

    reinterpret_cast muss in dem Fall nicht funktionieren.
    void* muss gross genug sein um alle möglichen T*, U* etc. "aufnehmen" zu können.
    Zeiger auf andere Typen, eben T*, U* etc., können (dürfen) allerdings kleiner als ein void* sein.
    Dadurch ist auch die Bit-Kompatibilität von T* zu void* bzw. umgekehrt nicht garantiert.

    Daher ist reinterpret_cast in dem Fall strenggenommen falsch.
    Daher ist static_cast angesagt.

    Und wann und wo ein Zeiger seine "interne Representation" ändert, bzw. ändern kann/darf, darüber mache ich mir schon länger keine Gedanken mehr 🙂



  • @SeppJ:
    Doch, genau das will er 🙂

    Ich verstehe auch nicht ganz was dich da auf eine falsche Fährte gelockt hat. Ist dir noch nie ein UserData Zeiger untergekommen?
    Ich hätte gedacht dass die Technik so verbreitet ist, dass sie jeder kennen sollte, und auch intuitiv verstehen.

    Ich kenne das schon vom Amiga OS, später dann von der WinAPI (meist als "dwUserdata" vom Typ "DWORD_PTR"), und selbst im .NET Framework trifft man es an einigen Stellen (Property "Tag" vom Type "object"). Und natürlich aus diversen anderen Libraries.



  • ................ schrieb:

    Es ist häufig so, dass C Librarys Callback Funktionen mit einem Parameter (Typ void) anbieten, um eigene Daten in der Funktion verfügbar zu machen.

    Das ganze ist also vollkommen in Ordnung und hat mit schlechtem Design rein gar nichts zu tun. Ganz im Gegenteil.

    Aus C-Sicht: Ja.

    Aus C++-Sicht ist das schlechtes Design. Moderne Callbacks sind typsicher und viel flexibler (schau dir mal boost::function und boost::bind an). Auch abstrakte Interfaces sind eine Möglichkeit. In C++ würde ich nicht void* einsetzen, wenn sich das Problem besser lösen lässt. Aber zur Kompatibilität mit C- oder älteren C++-APIs hat man eben keine Wahl.


Anmelden zum Antworten