endianness bestimmen



  • Hi,

    wie kann ich am besten zwischen little/big endian unterscheiden ohne eine externe lib zu verwenden?

    mein ansatz waere folgender, hat jedoch ein problem, falls int nicht 4 bytes ist... gehts noch besser? eventuell zur compiletime? ist die verwendung des unions undefined behavior?

    #include <iostream>
    using namespace std;
    
    bool is_little_endian() {
    	union {
    		int idata;
    		char cdata[4];
    	}endian_test;
    
    	endian_test.idata = 0x12345678;
    
    	if(endian_test.cdata[0] == 0x78) {
    		return true;
    	}
    
    	return false;
    }
    
    int main() {	
    	cout << is_little_endian() << endl;
    
    	return 0;
    }
    


  • Endianess und Standard vertragen sich nicht. Der Standard erlaubt beliebige Anordnungen der Bits und Bytes, nicht nur big und little endian. Strikt standardkonform kannst du Endianess definitiv nicht bestimmen.

    Wozu brauchst du das überhaupt? Ich habe noch nie einen Fall gesehen, mit dem man das nicht ohne lösen konnte.



  • vorbereitung coding interview...



  • Ich an deiner Stelle würde recherchieren, wieso man das Wissen nicht braucht und wie man darum arbeitet, und das dann sagen.



  • was wuerdest du machen, wenn der interviewer code sehen will?



  • +1 zu Nathan, erst zeigst du deinen Code und dann erklärst du, wieso man ihn nicht braucht.

    Zum Code: Ich würde uint32_t aus <cstdint> nehmen, dann ist das schonmal etwas sicherer. Und

    if(endian_test.cdata[0] == 0x78) {
            return true;
        }
    
        return false;
    

    würde ich lieber ohne if schreiben...
    Der Code ist natürlich UB, aber er sollte auf den üblichen Plattformen funktionierten.



  • ok.

    ohne if, wie meinst das? warum uint32_t?
    kann man endianness zur compile time bestimmen?



  • Dafür gibt es ntohl und so:

    bool is_big_endian = (ntohl(1) == 1);
    

    Die Funktion ist zwar nicht im Standard, aber jeder Compiler liefert die in irgendwelchen Headern mit.
    Keine Sorge, Compiler optimieren den Aufruf weg.

    Es gibt noch eine andere Möglichkeit.

    uint32_t test_value = 0x12345678;
    bool is_big_endian = (reinterpret_cast<char *>(&test_value)[0] == 0x12);
    

    Die "clevere" Lösung mit dem union würde ich nicht empfehlen, weil undefiniertes Verhalten für Menschen verwirrend ist. Eine portable Lösung erfordert nur C++-Kenntnisse, eine unportable mit union erfordert zusätzliche Plattformkenntnisse zum Verständnis.

    Man kann die Endianness zwar so bestimmen, aber meiner Erfahrung nach nützt diese Information außerhalb von Kernel-Treibern oder so nichts. Und da würde man das dann vernünftig über plattformspezifische Makros unterscheiden.
    Wofür glaubst du die Information denn zu benötigen?



  • coder007 schrieb:

    kann man endianness zur compile time bestimmen?

    http://stackoverflow.com/a/4240014
    Da ist auch eine Runtime-Methode, die zumindest nicht die strict aliasing rule verletzt. Lesen ist aber mindestens implementation-defined, wenn nicht ebenfalls UB.



  • @TyRoXx: das ntohl ist zwar nett zu erwaehnen, aber reicht wohl nicht aus, wenn der interviewer code sehen will...



  • TyRoXx schrieb:

    Man kann die Endianness zwar so bestimmen, aber meiner Erfahrung nach nützt diese Information außerhalb von Kernel-Treibern oder so nichts. Und da würde man das dann vernünftig über plattformspezifische Makros unterscheiden.
    Wofür glaubst du die Information denn zu benötigen?

    Z.B. für das lesen von Daten, die mit fwrite geschrieben wurden.



  • DirkB schrieb:

    TyRoXx schrieb:

    Man kann die Endianness zwar so bestimmen, aber meiner Erfahrung nach nützt diese Information außerhalb von Kernel-Treibern oder so nichts. Und da würde man das dann vernünftig über plattformspezifische Makros unterscheiden.
    Wofür glaubst du die Information denn zu benötigen?

    Z.B. für das lesen von Daten, die mit fwrite geschrieben wurden.

    Da kann man shiften und hat die Daten immer im selben Format.



  • DirkB schrieb:

    Z.B. für das lesen von Daten, die mit fwrite geschrieben wurden.

    So kann man das machen:

    uint32_t value = 0;
    fread(&value, sizeof(value), 1, file);
    value = ntohl(value);
    

    Wo genau musste ich nun die Endianness ermitteln?

    Falls auf einer absurden Plattform ntohl nicht verfügbar ist, kann man das hier nehmen:

    uint32_t ntohl(uint32_t big)
    {
    	unsigned char *bytes = (unsigned char *)&big;
    	return
    		(uint32_t(bytes[0]) << 24) |
    		(uint32_t(bytes[1]) << 16) |
    		(uint32_t(bytes[2]) <<  8) |
    		(uint32_t(bytes[3]));
    }
    

    Das geht immer, wenn unsigned char 8 Bits hat. Brauchte ich hier irgendwo die Endianness?
    Und was, wenn der Prozessor weder Big- noch Little-Endian hat, sondern eine andere Byte-Reihenfolge (ja, so etwas gibt es). Meine portable Lösung funktioniert auch da.



  • Und wenn die Daten nicht in der Network Byte Order vorhanden sind? ²

    ntohl vertauscht die Bytes auf Little Endian Systemen.
    Auf Big Endian Systemen wird nichts vertauscht.

    ² Klar, ist ein Designfehler. Aber so werden nunmal viele Daten von x86 Systemen gespeichert.



  • DirkB schrieb:

    Und wenn die Daten nicht in der Network Byte Order vorhanden sind?

    Dann dreht man die eben um.

    uint32_t swap_bytes_32(uint32_t value)
    {
    #ifdef __GNUC__
    	//plattformspezifische Optimierung
    	return __builtin_bswap32(value);
    #else
    	//portabler Fallback
    	char *data = reinterpret_cast<char *>(&value);
    	std::reverse(data, data + sizeof(value));
    	return value;
    #endif
    }
    
    uint32_t value = 0;
    fread(&value, sizeof(value), 1, file);
    value = ntohl(swap_bytes_32(value));
    

    Auch hier ist es nicht nötig die Endianness zu kennen.
    GCC erkennt das doppelte Vertauschen auf Little-Endian mit Intrinsics wie __builtin_bswap32 übrigens und optimiert es weg.

    EDIT: Ich hatte zuerst swap_bytes_32(ntohl(value)) geschrieben, aber das funktioniert nur auf Little- und Big-Endian-Plattformen.



  • union deadbeef - google einfach nach deadbeef, da sollte schon was dabei rauskommen.



  • coder007 schrieb:

    kann man endianness zur compile time bestimmen?

    Sollte funktionieren:

    constexpr bool is_little_endian()
    {
        union helper {
            int i;
            char c;
        };
        return helper{1}.c;
    }
    

    Edit: garantiert der Standard, dass helper::c im 1. Byte von helper liegt? Ansonsten wäre char c[sizeof(int)] und helper{1}.c[0] wohl ehr Standardkonform.



  • osdt schrieb:

    Edit: garantiert der Standard, dass helper::c im 1. Byte von helper liegt?

    Das ist vollkommen egal, da das Lesen des nicht aktiven Unionmembers sowieso UB ist.



  • osdt schrieb:

    Edit: garantiert der Standard, dass helper::c im 1. Byte von helper liegt?

    Damit ist das geklärt:

    N3337 $9.5 schrieb:

    ... Each non-static data member is allocated as if it were the sole member of a struct

    Nathan schrieb:

    Das ist vollkommen egal, da das Lesen des nicht aktiven Unionmembers sowieso UB ist.

    Das bezieht sich meines Wissens nur auf nicht-triviale Typen, deren Konstuktor explizit aufgerufen werden muss. Nur in diesem Zusmmenhang macht der Term 'aktiver Member' überhaupt Sinn. Ich lasse mich natürlich gern eines Besseren belehren.


  • Mod

    Das ist vollkommen egal, da das Lesen des nicht aktiven Unionmembers sowieso UB ist.

    Das ist (pauschal) offensichtlich falsch.

    Lesen ist aber mindestens implementation-defined, wenn nicht ebenfalls UB.

    Welches Lesen?


Anmelden zum Antworten