Unterschiedliche Endianness testen


  • Mod

    MrEndianness schrieb:

    Kann man eigentlich davon ausgehen, dass Windows immer Little-Endian ist? Oder ist diese Annahme nicht allgemeingültig?

    Derzeit ja, da es Windows nur noch für x86 gibt. Aber das ist nicht gesagt, dass sich das nicht irgendwann mal wieder ändert, MS hat in der Vergangenheit ja auch schon versucht auf andere Plattformen zu gehen. Und falls man es mal mit einem historischen Windows oder einer inoffiziellen Portierung oder einem Emulator zu tun hat, dann bekommt man ein Problem. Und würde man nicht auch ein Problem mit dem Unterschied zwischen 32 und 64 Bit Plattformen bekommen?

    Kurz: Die Annahme würde ich nicht machen, wenn man ohnehin einen Endianesstest programmiert dann sicherheitshalber für alle Plattformen.



  • Warum gefällt Dir eigentlich die Lösung nicht, bei der Du gar nicht wissen musst, in welcher Reihenfolge die Bytes eines ints abgelegt werden? Das ist doch das einfachste...

    ...
    inline uint_least32_t readU32le(const unsigned char *data)
    {
      return static_cast<uint_least32_t>(data[0])
      |     (static_cast<uint_least32_t>(data[1]) << 8)
      |     (static_cast<uint_least32_t>(data[2]) << 16)
      |     (static_cast<uint_least32_t>(data[3]) << 24);
    }
    ...
    

    Das funzt garantiert überall da, wo CHAR_BIT==8 gilt und man sich ein entsprechendes typedef für uint_least32_t "besorgt" hat -- echt jetzt. Die Daten der Binärdatei würdest Du in einen char-Puffer lesen und dann diese Funktion mit entsprechendem Zeiger einfach aufrufen und gut ist...


  • Mod

    Im Prinzip wäre es sogar sehr einfach dies auch auf nicht 8-Bit chars zu erweitern, indem man einfach CHAR_BIT statt 8 schreibt. Dann muss man nur darauf achten, dass der Zieltyp groß genug ist. Aber das muss man ohnehin immer.



  • SeppJ schrieb:

    Im Prinzip wäre es sogar sehr einfach dies auch auf nicht 8-Bit chars zu erweitern, indem man einfach CHAR_BIT statt 8 schreibt. Dann muss man nur darauf achten, dass der Zieltyp groß genug ist. Aber das muss man ohnehin immer.

    welche nachkriegs maschiene hat != 8 bit?



  • SeppJ schrieb:

    Im Prinzip wäre es sogar sehr einfach dies auch auf nicht 8-Bit chars zu erweitern, indem man einfach CHAR_BIT statt 8 schreibt.

    Nun, wenn Dateiformat XY auf Basis von Oktetten definiert ist und Du auf der PDP-11 das Ding einlesen willst, welche mit 9-Bit chars arbeitet, dann bezweifle ich, ob das Ersetzten der 8 durch CHAR_BIT überhaupt korrekt ist. Es könnte ja auch sein, dass man irgendwie an einen char-Puffer rankommt, wo nur die untersten 8 Bit gesetzt sind und das oberste kein Datenbit sondern ein Füllbit ist. Dann wäre die 8 immer noch korrekt. Aber das ist alles zu hypothetisch für meinem Geschmack. Wenn das Programm auf so einem Exoten kompiliert werden soll, dann muss man eben nochmal Hand anlegen. Da ich mich mit so etwas nicht auskenne, mache ich mir auch nicht die Mühe, CHAR_BIT!=8 Fälle zu berücksichtigen. 🙂


  • Mod

    no_code schrieb:

    welche nachkriegs maschiene hat != 8 bit?

    Irgendwer hatte hier mal geschrieben, dass ihm eine Maschine mit 64-Bit Bytes begegnet ist. ICh finde den Beitrag leider gerade nicht, aber es war irgendwann während der letzten paar Monate. Das war irgendeine Spezialmaschine mit extra großem Speicher.

    Aber kümelkackers Einwand ist gerechtfertigt, dass man dann wahrscheinlich sowieso nochmal Hand anlegen muss, weil die Annahme 1 Byte = 8 Bit an zu vielen Stellen implizit gemacht wird.



  • SeppJ schrieb:

    64-Bit Bytes

    und ich dachte das wär ein schreib fehler 😉

    btw. meine 8bit bezogen sich natürlich auf CHAR_BIT...


  • Mod

    no_code schrieb:

    SeppJ schrieb:

    64-Bit Bytes

    und ich dachte das wär ein schreib fehler 😉

    btw. meine 8bit bezogen sich natürlich auf CHAR_BIT...

    Hab's gefunden:
    http://www.c-plusplus.net/forum/viewtopic-var-t-is-265718-and-postorder-is-asc-and-start-is-10.html
    Waren 16-Bit Bytes, short war 64 Bit.



  • krümelkacker schrieb:

    Warum gefällt Dir eigentlich die Lösung nicht, bei der Du gar nicht wissen musst, in welcher Reihenfolge die Bytes eines ints abgelegt werden? Das ist doch das einfachste...

    ...
    inline uint_least32_t readU32le(const unsigned char *data)
    {
      return static_cast<uint_least32_t>(data[0])
      |     (static_cast<uint_least32_t>(data[1]) << 8)
      |     (static_cast<uint_least32_t>(data[2]) << 16)
      |     (static_cast<uint_least32_t>(data[3]) << 24);
    }
    ...
    

    Das funzt garantiert überall da, wo CHAR_BIT==8 gilt und man sich ein entsprechendes typedef für uint_least32_t "besorgt" hat -- echt jetzt. Die Daten der Binärdatei würdest Du in einen char-Puffer lesen und dann diese Funktion mit entsprechendem Zeiger einfach aufrufen und gut ist...

    Es ist nicht so, dass es mir nicht gefällt. Es ist viel mehr so, dass ich (wie anfangs erwähnt) nicht so sicher im Rumschubsen von Bytes bin. 😃
    Mir war also gar nicht bewusst, dass diese Funktion bereits eine geeignete Lösung ist, sorry.

    Was macht diese Funktion denn genau? Ich kann also mit fread einfach ein char[4] lesen und übergebe ihn dann dieser Funktion und ich erhalte eine Zahl mit der ich rechnen kann. Egal welche Endianness der verwendete Computer hat?

    Ist es wirklich so einfach? Wenn ja, warum funktioniert diese Funktion überhaupt? Was ist der Trick? 🙂



  • Der Operator << schiebt die Bits in Richtung der größeren Zahl. (soweit ich das verstehe)

    Also:

    die 32 Bits:                         deine Ankommenden Bits:
    00000000 00000000 00000000 00000000  10101001 <<  8
    00000000 00000000 00000000 10101001  00011001 << 16
    00000000 00000000 00011001 10101001  usw...
    

  • Mod

    MrEndianness schrieb:

    Es ist nicht so, dass es mir nicht gefällt. Es ist viel mehr so, dass ich (wie anfangs erwähnt) nicht so sicher im Rumschubsen von Bytes bin. 😃
    Mir war also gar nicht bewusst, dass diese Funktion bereits eine geeignete Lösung ist, sorry.

    Was macht diese Funktion denn genau? Ich kann also mit fread einfach ein char[4] lesen und übergebe ihn dann dieser Funktion und ich erhalte eine Zahl mit der ich rechnen kann. Egal welche Endianness der verwendete Computer hat?

    Ist es wirklich so einfach? Wenn ja, warum funktioniert diese Funktion überhaupt? Was ist der Trick? 🙂

    Der Linksschiebeoperator << x ist eigentlich nur eine elegante Möglichkeit um zu sagen "multipliziere die Zahl mit 2 hoch x". Also übersetzt:

    return static_cast<uint_least32_t>(data[0] * pow(2,0)))
      |     (static_cast<uint_least32_t>(data[1]) * pow(2,8))
      |     (static_cast<uint_least32_t>(data[2]) * pow(2,16))
      |     (static_cast<uint_least32_t>(data[3]) * pow(2,24));
    

    Nun erinnern wir uns an die Potenzgesetze und nutzen, dass 2^(a*b) = 2ab ist. Somit kann man schreiben:

    return static_cast<uint_least32_t>(data[0] * pow(pow(2,8),0))) //  0 = 8*0
      |     (static_cast<uint_least32_t>(data[1]) * pow(pow(2,8),1))  // 8 = 8 * 1
      |     (static_cast<uint_least32_t>(data[2]) * pow(pow(2,8),2))  //16 = 8 * 2
      |     (static_cast<uint_least32_t>(data[3]) * pow(pow(2,8),3)); //24 = 8 * 3
    

    Jetzt setzen wir 2^8=256 ein:

    return static_cast<uint_least32_t>(data[0] * pow(256,0))) 
      |     (static_cast<uint_least32_t>(data[1]) * pow(256,1)) 
      |     (static_cast<uint_least32_t>(data[2]) * pow(256,2)) 
      |     (static_cast<uint_least32_t>(data[3]) * pow(256,3));
    

    Wenn wir uns jetzt erinnern, dass CHAR_BIT=8 ist, dann fällt uns auf, größte Wert den ein char mit 8 Bit haben kann 256 ist, denn 2^8=256. Und jetzt gucken wir mal im Binärsystem welche Werte data[0] * pow(256,0) haben kann:

    pow(256,0) ist einfach 1 (binär wie dezimal)
    255 ist in binär 11111111
    Wenn wir 1 mit werten zwischen 0 und 11111111 malnehmen bekommen wir 
    logischerweise nur Werte zwischen 0 und 11111111 heraus.
    
    Fein, gucken wir nun data[1] * pow(256,1) an:
    pow(256,1) ist 256, in binär 100000000 (eine 1 mit 8 Nullen)
    Was kommt raus, wenn wir eine Zahl zwischen 0 und 11111111 (255, acht Einsen)
     mit 100000000 multiplizieren?
    Die Regel, dass man eine Zahl mit 10 multipliziert indem man eine Null 
    dranhängt gilt auch im binären (sogar in allen Zahlensystemen, rechne mal 
    nach), das heißt z.B. 110101 * 10 = 1101010 (dezimal steht da: 53 * 2 = 106. 
    Passt.). Das gilt natürlich auch für Multiplikation mit 100: 1101 * 100 = 
    110100 (13*4=52 Passt.) Und so weiter.
    Das heißt,  wenn wir mit 100000000 multiplizieren hat das Ergebnis am Ende 8 
    Nullen. Was ist die größte Zahl die wir erhalten können?
    11111111 * 100000000 = 11111111 00000000
    Und der kleinste Wert (abgesehen von Null)?
    00000001 * 100000000 = 00000001 00000000
    
    Analog sehen wir, dass die dritte Zeile der Rechnung immer nur Werte mit 16 
    Nullen am Ende und die vierte mit 24 Nullen am Ende ergibt.
    
    Jetzt gucken wir aber mal, was passiert, wenn man den ODER Operator | benutzt 
    um zum Beispiel die erste mit der zweiten Zeile zu verknüpfen:
    Die erste Zeile hat nur Werte der Form
    00000000 XXXXXXXX  (X ist 1 oder 0)
    Die zweite Zeile hat nur Werte der Form
    YYYYYYYY 00000000
    Oho, da kommt also bei ODER-Verknüpfung dies heraus:
    YYYYYYYY XXXXXXXX
    
    (Jetzt sieht man auch schon so langsam worauf das hinausläuft: Wenn der char 
    data[0] vorher den Wert XXXXXXXX und der char[1] vorher den Wert YYYYYYYY 
    hatte, dann ist das Ergebnis YYYYYYYY XXXXXXXX genau das Bitmuster das wir 
    haben wollten.)
    

    Formell haben wir da eine Addition durchgeführt (nachrechnen!), d.h. man kann den ODER Operator auch durch ein + ersetzen:

    return static_cast<uint_least32_t>(data[0] * pow(256,0))) 
      +     (static_cast<uint_least32_t>(data[1]) * pow(256,1)) 
      +     (static_cast<uint_least32_t>(data[2]) * pow(256,2)) 
      +     (static_cast<uint_least32_t>(data[3]) * pow(256,3));
    

    In dieser Schreibweise sehen wir auch, dass hier ein Stellenwertsystem mit der Basis 256 vorliegt. Die Werte in den chars sind die Ziffern und wir berechnen nach den üblichen Formeln den Wert einer Zahl aus ihrer Zifferndarstellung. Genauso wie wir den Wert der Hexadezimalzahl A2D3 berechnen würden (A ist 10 im Dezimalsystem und D ist 13):
    A2D3 = 10 * 16 ^ 3 + 2 * 16 ^ 2 + 13 * 16 ^ 1 + 3 * 16 ^ 0 = 10*4096 + 3*256 + 13*16 + 3*1 = 41 683
    Nur hier eben mit 256 als Basis und nicht 16. Und Endianess haben wir nirgendwo benutzt, bloß gutes altes Potenzieren, Multiplikation und Addition. Das heißt, das Ergebnis ist völlig unabhängig von der internen Darstellung der Zahlen.

    Man probiere übrigens alle hier gezeigten Programme einmal aus und überzeuge sich, dass sie tatsächlich für jeden Umformungsschritt das gleich Ergebnis liefern.

    P.S.: Autsch, das ist lang geworden, das werde ich nicht alles Korrektur lesen. Wenn jemand Fehler sieht oder Verbesserungsvorschläge hat, bitte melden.



  • Wow, SeppJ. Danke für deine große Bemühung mit das so genau zu erklären.
    Das hilft mir unglaublich weiter. Viele vielen Dank. 👍


Anmelden zum Antworten