Binärdaten aus Datei einlesen



  • @Th69 sagte in Binärdaten aus Datei einlesen:

    Und dein Code ist ja noch umständlicher (warum erst einen anderen Puffer verwenden und dann umkopieren?).

    Damit ich sicher bin, dass ich kein undefiniertes Verhalten habe.



  • Bei meinem Code gibt es kein UB, das gibt es ja auch als Beispiel im Link.



  • Hi(gh)!

    @Th69 sagte in Binärdaten aus Datei einlesen:

    @wob, da hast du Recht, muß natürlich

    Source.read(reinterpret_cast<char*>(&val), sizeof(int));
    

    Das hat perfekt funktioniert! Danke, Th69 und wob... rechnet schonmal damit, dass demnächst im Khyberspace zwei bislang namenlose Viertausender im Mittleren Hindukusch nach euch benannt werden! Koh-e Wob, das klingt doch cool, oder?

    Auch die Ausgabe fördert ziemlich genau das zutage, was ich erwartet habe:

    Value #0: 834527424
    Value #1: 1426715008
    Value #2: 1363809664
    Value #3: 1600095488
    Value #4: 2050849408
    Value #5: 1882493696
    Value #6: 1344495744
    Value #7: 2143818880
    Value #8: 1536834176
    Value #9: 632175360
    

    Zum Vergleich die Darstellung der Testdatei in Audacity...

    Bis bald im Khyberspace!

    Yadgar



  • @Yadgar sagte in Binärdaten aus Datei einlesen:

    Hi(gh)!
    rechnet schonmal damit, dass demnächst im Khyberspace zwei bislang namenlose Viertausender im Mittleren Hindukusch nach euch benannt werden! Koh-e Wob, das klingt doch cool, oder?
    Bis bald im Khyberspace!

    Boah, was ein Schwachsinn.



  • @wob

    Source.read(reinterpret_cast<char*>(&val), sizeof(int));
    

    Damit ich sicher bin, dass ich kein undefiniertes Verhalten habe.

    Wenn val nicht vom Typ

    char
    signed char
    unsigned char
    void
    

    ist, ist es UB;
    da helfen auch keine obskuren C++ Typecasts mehr.



  • @Wutz Du redest Blödsinn. Wieder.



  • @Swordfish
    Und du kennst den C Standard nicht - redest aber trotzdem immer wieder darüber.



  • @Wutz sagte in Binärdaten aus Datei einlesen:

    Und du kennst den C Standard nicht

    Der ist hier in der C++-Gruppe irrelevant. Hier gilt C++-Standard. Dennoch!

    Ich hatte das in C++ auch so in Erinnerung wie @Wutz es sagt und habe daher in ein char-Array gelesen. Das ist nämlich auf jeden Fall erlaubt.

    Interessant ist aber, dass cppreference den reinterpret_cast hier https://en.cppreference.com/w/cpp/io/basic_istream/read im Beispiel verwendet. Daher habe ich mich gefragt, ob es irgendwo eine Spezialregel gibt, die das dennoch erlaubt.

    Aber egal, habe ich mir gedacht, ich lese einfach in das char-Array und bin damit auf der sicheren Seite.


  • Mod

    @Wutz sagte in Binärdaten aus Datei einlesen:

    @wob

    Source.read(reinterpret_cast<char*>(&val), sizeof(int));
    

    Damit ich sicher bin, dass ich kein undefiniertes Verhalten habe.

    Wenn val nicht vom Typ

    char
    signed char
    unsigned char
    void
    

    ist, ist es UB;

    Stimme ich nicht zu. Weil wir hier die object representation eines (gueltigen) int Werts in ein neues int Objekt "kopieren". [basic]:

    For any object (other than a potentially-overlapping subobject) of trivially copyable type T, whether or not the object holds a valid value of type T, the underlying bytes ([intro.memory]) making up the object can be copied into an array of char, unsigned char, or std​::​byte ([cstddef.syn]). If the content of that array is copied back into the object, the object shall subsequently hold its original value.

    Der "juristische" Knackpunkt liegt eben darin, ob istream::read als Kopierfunktion betrachtet wird, oder nicht. P0593 scheint hier anzudeuten, dass memcpy und memmove, die beide in Bezug auf den obigen Paragraph parenthetisch erwaehnt werden, eine Sonderrolle einnehmen (wobei das 100%-ig noch weiter gelockert wird). Das aendert aber nichts an der Praxis, in der solcher Code gerne auch mal seine eigenen Funktionen einsetzt. Und 'juristisch' betrachtet wird eben in diesem Paragraphen von keiner konkreten Funktion gesprochen.

    Eine sauberere Variante waere sicherlich, read auf ein std::byte Array anzuwenden, und dieses per memcpy in einen int zu kopieren. Da wird jeder Compiler auch den gleichen Code erzeugen.



  • @Columbo
    Ich glaube das mit memmove & Co in P0593 ist für Fälle gedacht wo im Ziel der Kopieroperation noch kein T existiert und auch keine Operation vorangeht die implizit Objekte erzeugen würde.

    Also z.B. sowas

    void fun(void* t, void* u) {
        unsigned char buf[1234];
    
        memcpy(buf, t, sizeof(T);
        T* t2 = reinterpret_cast<T*>(buf);
        t2->foo(); // OK ohne die Spezialregel weil das Erzeugen des Arrays "buf" hier implizit Objekte erzeugt
    
        memcpy(buf, u, sizeof(U);
        U* u2 = reinterpret_cast<T*>(buf);
        u2->zooStation(); // ohne die Spezialregel nicht OK - das Erzeugen von "buf" hat hier implizit 
                          // magisch ein für oben passendes T erzeugt, aber halt kein U
    }
    

    Wenn ein passendes Objekt schon existiert, dann sollte der von dir zitierte Paragraph ausreichend sein. Die Frage ist dann wirklich nur noch was man unter Kopieren von Speicher versteht. MMn. sollte da auch das "manuelle" Kopieren mittels char/unsigned char/std::byte Zeiger gelten. Denn sonst hat einfach verdammt viel Code undefiniertes Verhalten.


  • Mod

    @hustbaer sagte in Binärdaten aus Datei einlesen:

    Denn sonst hat einfach verdammt viel Code undefiniertes Verhalten.

    Genau. P0593/P0137 sind auch lediglich ein Versuch des Komitees, die Luecke zwischen Standard und de facto Praxis zu schliessen. Das geschieht aber unweigerlich ausgehend von der Praxis, weshalb @Wutz's Kommentar hier doppelt falsch ist (sowohl von der juristischen als anwendenden Perspektive). Der Standard dient primaer als Abstraktion etablierter Praxis, welche im Falle von object lifetime semantics so vielgestaltig ist. Gerade weil C++ eben als maschinennahe Sprache angewandt wird, das Typsystem so leicht zu umgehen ist, und Compiler in vielen Faellen tun was man erwartet (auch wenn diese Faelle ggf. nicht definiert sind oder intendiert sind), war es ja von vornherein klar, dass jeder herumcastet und macht was er will, und die standardisierte Sprache das 20 Jahre spaeter ausbuegeln darf.



  • Ich hab mal ausprobiert was aktuelle Compiler sich da an Optimierungen wirklich trauen.

    #include <stddef.h>
    
    struct Foo {
        int x;
    };
    
    struct EightBits {
        unsigned char bits : 8;
    };
    
    #if 1
    using X = char;
    #else
    using X = EightBits;
    #endif
    
    void fun(int* arr, Foo* foo, void* mightAliasFoo) {
        arr[0] = foo->x;
    
        X* f = static_cast<X*>(mightAliasFoo);
        for (int i = 0; i < sizeof(Foo)/sizeof(X); i++)
            f[i] = {};
    
        arr[1] = foo->x;
    }
    

    https://godbolt.org/z/E4jK1qdW8

    MSVC, ICC, ICX und Clang sind feige, und erzeugen in beiden Fällen ein 2. Load für arr[1] = foo->x. GCC 10 ist mutiger und erzeugt das 2. Load nur mit X = char aber nicht mit X = EightBits.

    Auf jeden Fall erzeugen alle Code der funktioniert so lange man halt char (oder unsigned char oder std::byte) zum Kopieren/Überschreiben verwendet.


Anmelden zum Antworten