Endianness



  • Hallo,

    obwohl ich inzwischen einige Threads zum Thema Endianness gelesen habe und mir inzwischen prinzipiell klar geworden ist, was es damit auf sich hat, bin ich immer noch ein wenig verwirrt.

    Ich möchte Daten aus einem File lesen, das (4 byte) float-Werte in Little Endian byte-order enthält. Dazu hole ich mir 4 bytes an der entsprechenden Stelle des Files und wandele diese per reinterpret_cast in float um, was auch problemlos funktioniert. Ich programmiere an einem PC, der ebenfalls Little Endian verwendet.

    Mir ist nun nicht ganz klar, was passieren würde, wenn man das fertige Programm auf einem Rechner ausführen würde, der Big Endian verwendet.

    - Würde das einfach ebenfalls problemlos funktionieren, ohne, dass ich etwas tun müsste?
    - Müsste ich die Endianness des Rechners in meine Code überprüfen, die Bytes im Fall von Big Endian entsprechend umsortieren und dann wäre wieder alles klar?
    - Könnte ich stattdessen auf einem Big Endian Rechner einfach aus einem Big Endian file (hätte ich ebenfalls zur Verfügung) lesen?

    Nach dem, was ich bisher gefunden habe, habe ich es so verstanden, dass die Möglichkeiten 2 und 3 beide funktionieren müssten, wäre aber dankbar, wenn mir das jemand bestätigen könnte.
    Ich glaube, mein eigentliches Problem ist, dass mir nicht wirklich klar ist, was der reinterpret_cast eigentlich treibt.

    Danke schonmal vorab!



  • Nanuq schrieb:

    Nach dem, was ich bisher gefunden habe, habe ich es so verstanden, dass die Möglichkeiten 2 und 3 beide funktionieren müssten, wäre aber dankbar, wenn mir das jemand bestätigen könnte.

    bestätigt.

    Ich glaube, mein eigentliches Problem ist, dass mir nicht wirklich klar ist, was der reinterpret_cast eigentlich treibt.

    Garnichts. reinterpret_cast fasst deine Daten nicht an. Es sagt dem Compiler nur:

    interpretiere diese bytes als diesen Typen.

    reinterpret_cast ist nur ein syntax konstrukt dass die sprache braucht.



  • Danke für die schnelle Antwort, das hilft mir weiter!

    Man kann also sagen, dass der reinterpret_cast auf einem Big Endian Rechner davon ausgeht, dass die Bytes gemäß Big Endian sortiert sind und sie entsprechend umwandelt und auf einem Little Endian Rechner eben andersherum?



  • Nanuq schrieb:

    Danke für die schnelle Antwort, das hilft mir weiter!

    Man kann also sagen, dass der reinterpret_cast auf einem Big Endian Rechner davon ausgeht, dass die Bytes gemäß Big Endian sortiert sind und sie entsprechend umwandelt und auf einem Little Endian Rechner eben andersherum?

    reinterpret_cast geht von gar nichts aus 😉 Es ist eine Brechstangenmethode um daten syntaktisch anders zu representieren. Das hat keinerlei Effekt auf die Daten im Speicher selbst.



  • Ok, das hatte ich schlecht formuliert. Anstatt "umwandeln" hätte ich "interpretieren" schreiben sollen, aber ich habe verstanden, dass die eigentlichen Daten unangetastet bleiben.

    evilissimo schrieb:

    Brechstangenmethode

    Ist der reinterpret_cast denn dann trotzdem in diesem Fall das Mittel der Wahl, oder sollte man das besser anders lösen?



  • Es gibt so funktionen wie ntohl/nthos die dir integer von Network Byte Order in Host Byte Order konvertieren. Auf nem BigEndian system wären im Grunde NOP.

    Du musst ja nicht alles konvertieren, es kommt immer drauf an wie die Daten gespeichert werden. UTF-8 Strings oder ASCII strings müssten ja nicht konvertiert werden. Integerale von 2 und mehr Byte hingegen schon.

    Also kommt es immer auf deine Daten an und wie du die strukturiert hast.



  • Da ich das File, das ich lese, nicht selbst generiere, werde ich dann wohl bei meinem reinterpret_cast bleiben und die Endianness (der Vollständigkeit halber) entsprechend berücksichtigen.

    Danke nochmal für die Antworten!



  • Das hat doch damit nichts zu tun. Wenn du weißt das die Datei in Network Byte Order erstellt wurde. Dann musst du die Daten dem entsprechend auch nach dem auslesen noch konvertieren bevor du sie verwenden kannst.

    Das reinterpret_cast brauchst du vielleicht um aus dem int i = 0; ein char * p = reinterpret_cast< char *> (&i); zu machen bevor du es zum auslesen übergibst an ifstream::read() z.B. aber du musst dennoch im nachhinein die daten konvertieren wenn das system auf dem deine Anwendung läuft nicht die richtig endianess hat. Sonst hast du hinterher nur Daten die nicht richtig ausgwertet werden können.



  • Bis eben dachte ich eigentlich, ich hätte es einigermaßen verstanden...

    Ich habe doch etwas in dieser Art:

    float readFloat(ifstream& file)
    {
        char bytes[4];
        for (int i = 0; i < 4; i++)
            bytes[i] = file.get();
    
        return *reinterpret_cast<float*>(bytes);
    }
    

    Damit der reinterpret_cast ein sinnvolles Ergebnis liefert, muss ich doch entweder die Bytes (beim Einlesen) entsprechend umsortieren, wenn die Endianess im File anders ist als auf dem System, oder eben aus einem File lesen, in dem Endianness der des Sytems entspricht, oder? 😕



  • Nanuq schrieb:

    Bis eben dachte ich eigentlich, ich hätte es einigermaßen verstanden...

    Ich habe doch etwas in dieser Art:

    float readFloat(ifstream& file)
    {
        char bytes[4];
        for (int i = 0; i < 4; i++)
            bytes[i] = file.get();
    
        return *reinterpret_cast<float*>(bytes);
    }
    

    Damit der reinterpret_cast ein sinnvolles Ergebnis liefert, muss ich doch entweder die Bytes (beim Einlesen) entsprechend umsortieren, wenn die Endianess im File anders ist als auf dem System, oder eben aus einem File lesen, in dem Endianness der des Sytems entspricht, oder? 😕

    Ja, einfach ein

    std::reverse(bytes, bytes+sizeof(bytes));
    

    machen, wenn die Byteorder der Datei und des Systems nicht übereinstimmt.

    Problematisch wird es natürlich nur, wenn die Darstellung eines floats nicht die gleiche ist ;). Aber dank IEEE754 sollte das nur auf obskuren Systemen passieren.



  • Es würde wohl eher so in der art aussehen:

    float readFloat(ifstream& file)
    {
       boost::  result = 0;
       file.read( reinterpret_cast< char * >( &result ), sizeof(int32_t) );   
       result = ntohl(result); // Von BigEndian zu HostByteOrder (Mehr oder weniger NOP wenn es auf einer BigEndian platform läuft )
       return *reinterpret_cast<float*>(&result);
    }
    

    Wobei mir das mit dem float nicht wirklich gefällt. Da davon ausgegangen wird das es 32 Bit hat, was nicht umbedingt richtig ist.


  • Mod

    rüdiger schrieb:

    Problematisch wird es natürlich nur, wenn die Darstellung eines floats nicht die gleiche ist ;). Aber dank IEEE754 sollte das nur auf obskuren Systemen passieren.

    Undefiniert ist es in jedem Fall, weil ein float gelesen wird, wo keins ist. Bedenkenswert bei Compilern wie gcc, die recht agressiv mit Aliasing umgehen.

    float readFloat(ifstream& file)
    {
        float value;
        for (int i = 0; i < sizeof value; i++)
            reinterpret_cast<char(&)[sizeof value]>(value)[i] = file.get();
    
        return value;
    }
    

    ist hingegen unproblematisch (gleiche Kodierung vorausgesetzt).



  • camper schrieb:

    Undefiniert ist es in jedem Fall, weil ein float gelesen wird, wo keins ist. Bedenkenswert bei Compilern wie gcc, die recht agressiv mit Aliasing umgehen.

    Was hilft uns das Bedenken von "aggresive aliasing" wenn wir so einlesen müssen weil es die API nicht anders zulassen würde?

    Das ist etwas was mir nicht so richtig in den Kopf will. Wenns umbedingt sein muss nimmt man dann halt ein union. (Was soweit ich das verstanden habe auch nicht wirklich viel hilft beim gcc)

    float readFloat(ifstream& file)
    {
        float value;
        for (int i = 0; i < sizeof value; i++)
            reinterpret_cast<char(&)[sizeof value]>(value)[i] = file.get();
    
        return value;
    }
    

    ist hingegen unproblematisch (gleiche Kodierung vorausgesetzt).

    Trotzdem musst du hier noch die endianess ändern oder nicht?
    Und lesen byte für byte kann auch nicht das wahre sein...



  • Da habe ich ja eine interessante Diskussion angestoßen...

    camper schrieb:

    rüdiger schrieb:

    Problematisch wird es natürlich nur, wenn die Darstellung eines floats nicht die gleiche ist ;). Aber dank IEEE754 sollte das nur auf obskuren Systemen passieren.

    Undefiniert ist es in jedem Fall, weil ein float gelesen wird, wo keins ist. Bedenkenswert bei Compilern wie gcc, die recht agressiv mit Aliasing umgehen.

    float readFloat(ifstream& file)
    {
        float value;
        for (int i = 0; i < sizeof value; i++)
            reinterpret_cast<char(&)[sizeof value]>(value)[i] = file.get();
    
        return value;
    }
    

    ist hingegen unproblematisch (gleiche Kodierung vorausgesetzt).

    Wenn ich das richtig verstehe, wird hier durch den reinterpret_cast ein float als eine Referenz auf ein char-Array selber Größe interpretiert und dessen einzelne Elemente byte-weise (bzw. char-weise) befüllt. Hätte ich ein Big Endian System aber ein Little Endian File, müsste ich eben in umgekehrter Reihenfolge befüllen, richtig?

    Letzten Endes funktioniert das so für mich, mein Progrämmchen wird ohnehin nicht zu sämtlichen Systemen kompatibel sein und mich wird auch niemand zur Verantwortung ziehen, wenn mal etwas schief geht ;).
    Eine spitzfindige Kleinigkeit würde mich aber trotzdem noch interessieren. Ich weiß, dass ein Wert in der zu lesenden Datei 4 Byte entspricht (laut Erzeuger des Files eigentlich "REAL*4"). Sollte ich dann nicht in jedem Fall 4 byte einlesen? Das sizeof(value) im obigen Fall dient doch vermutlich dazu auch Fälle zu berücksichtigen in denen ein float einmal nicht 32 Bit hat (oder gibt es das gar nicht?). Wie ich dann die 4 Byte in dieses "Nicht 4 Byte float" konvertiere möchte ich dann eigentlich gar nicht mehr wissen, glaube ich.

    Danke jedenfalls für eure Erläuterungen!



  • camper schrieb:

    rüdiger schrieb:

    Problematisch wird es natürlich nur, wenn die Darstellung eines floats nicht die gleiche ist ;). Aber dank IEEE754 sollte das nur auf obskuren Systemen passieren.

    Undefiniert ist es in jedem Fall, weil ein float gelesen wird, wo keins ist. Bedenkenswert bei Compilern wie gcc, die recht agressiv mit Aliasing umgehen.

    float readFloat(ifstream& file)
    {
        float value;
        for (int i = 0; i < sizeof value; i++)
            reinterpret_cast<char(&)[sizeof value]>(value)[i] = file.get();
    
        return value;
    }
    

    ist hingegen unproblematisch (gleiche Kodierung vorausgesetzt).

    Ist char* nicht die Ausnahme bei den Aliasing-Regeln? (Der GCC warnt auch zumindest nicht)



  • Wie wäre damit, die Daten ins ASCII-Format zu serialisieren? Dann gibt es solche Probleme nicht.


  • Mod

    rüdiger schrieb:

    Ist char* nicht die Ausnahme bei den Aliasing-Regeln?

    Richtig, was der Grund dafür ist, dass wenn an der Stelle zuvor ein float definiert wurde, sich dort auch hinterher ein float befindet (bzw. befinden könnte) und dank des char-Kopierens ist der Inhalt auch nicht mehr unbestimmt.
    Wurde dagegen ein char-Array deklariert, entsteht an dieser Stelle nicht plötzlich dadurch ein float, dass man das Array zeichenweise befüllt.


Anmelden zum Antworten