Wie schreibe ich Endian-agnostischen C/C++-Code?

Lesezeit: 11 Minuten

Ich habe etwas gegoogelt und konnte keinen guten Artikel zu dieser Frage finden. Was muss ich bei der Implementierung einer App beachten, die Endian-agnostisch sein soll?

  • Achten Sie auf Liliputionen.

    – NominSim

    21. Dezember 2012 um 17:38 Uhr

  • Verwenden Sie beim Persistieren/Übertragen von Daten entweder ein Textformat (vom Menschen lesbar) oder konvertieren Sie Ganzzahlen in ein plattformunabhängiges Format (dh Netzwerk-Byte-Reihenfolge), wenn Sie ein Binärformat verwenden müssen.

    – Martin York

    21. Dezember 2012 um 17:44 Uhr

  • Abstimmung zum Schließen als zu weit gefasst. Genauere Frage: stackoverflow.com/questions/105252/…

    – Ciro Santilli OurBigBook.com

    8. April 2016 um 20:41 Uhr

Benutzeravatar von Carl Norum
Karl Norum

Die einzige Zeit, in der Sie sich um Endianness kümmern müssen, ist, wenn Sie Endian-empfindliche Binärdaten (d. h. keinen Text) zwischen Systemen übertragen, die möglicherweise nicht die gleiche Endianness haben. Die normale Lösung ist die Verwendung von “Netzwerk-Byte-Reihenfolge” (auch bekannt als Big-Endian), um Daten zu übertragen, und swizzle dann die Bytes bei Bedarf am anderen Ende.

Um von der Host- in die Netzwerk-Byte-Reihenfolge umzuwandeln, verwenden Sie htons(3) und htonl(3). Um zurück zu konvertieren, verwenden Sie ntohl(3) und ntohs(3). Probier das aus Manpage für alles, was Sie wissen müssen. Für 64-Bit-Daten sind diese Frage und Antwort hilfreich.

  • Obwohl viele Binärprotokolle und Dateiformate jetzt dazu neigen, Little Endian zu sein, sind fast alle Systeme Little Endian. Es ist einfach viel einfacher, Little Endian zu verwenden, und wenn Sie jemals auf ein Big Endian-System stoßen sollten, lassen Sie den Code auf dieser Maschine konvertieren.

    – edA-qa mort-ora-y

    21. Dezember 2012 um 17:49 Uhr

  • edA, Ihr Vorschlag, “ein Big-Endian-System Code auf dieser Maschine konvertieren zu lassen”, zwingt Sie dazu, speziellen Code auf diesem einen Systemtyp zu haben. Es ist dann nicht “Endian-agnostisch”. Ignorieren, dass es ein Problem gibt, löst das Problem meistens nicht.

    – aaaa bbbb

    21. Dezember 2012 um 18:16 Uhr

  • Es ist nicht das einzige Mal. Die CPU ist am effizientesten, wenn sie Daten in ihrer korrekten Endianness empfängt, daher muss jeder Algorithmus, der sowohl Byte-Indizierung als auch ganzzahlige arithmetische Operationen mit denselben Daten durchführen muss, empfindlich auf Endianness sein.

    – Andreas Tomazos

    21. Dezember 2012 um 18:22 Uhr

  • Garantiert POSIX, dass die Netzwerk-Byte-Reihenfolge Big Endian ist?

    – Ciro Santilli OurBigBook.com

    8. April 2016 um 20:40 Uhr

  • @CiroSantilli六四事件法轮功包卓轩, vermutlich definieren das die Netzwerkspezifikationen, nicht POSIX.

    – Karl Norum

    8. April 2016 um 21:20 Uhr

Benutzeravatar von nos
Nr

Was muss ich bei der Implementierung einer App beachten, die Endian-agnostisch sein soll?

Man muss erst erkennen, wann Endian zum Thema wird. Und es wird meistens zu einem Problem, wenn Sie Daten von irgendwo extern lesen oder schreiben müssen, sei es das Lesen von Daten aus einer Datei oder die Netzwerkkommunikation zwischen Computern.

In solchen Fällen ist Endianness für Integer größer als ein Byte wichtig, da Integer auf verschiedenen Plattformen unterschiedlich im Speicher dargestellt werden. Dies bedeutet, dass Sie jedes Mal, wenn Sie externe Daten lesen oder schreiben müssen, mehr tun müssen, als nur den Speicher Ihres Programms zu leeren oder Daten direkt in Ihre eigenen Variablen zu lesen.

zB wenn Sie dieses Code-Snippet haben:

unsigned int var = ...;
write(fd, &var, sizeof var);

Sie schreiben den Speicherinhalt direkt aus varwas bedeutet, dass die Daten überall so präsentiert werden, wie sie im Speicher Ihres eigenen Computers dargestellt werden.

Wenn Sie diese Daten in eine Datei schreiben, ist der Dateiinhalt unterschiedlich, unabhängig davon, ob Sie das Programm auf einer Big-Endian- oder einer Little-Endian-Maschine ausführen. Dieser Code ist also nicht Endian-agnostisch, und Sie sollten solche Dinge vermeiden.

Konzentrieren Sie sich stattdessen auf das Datenformat. Entscheiden Sie beim Lesen/Schreiben von Daten immer zuerst das Datenformat und schreiben Sie dann den Code, um damit umzugehen. Dies wurde möglicherweise bereits für Sie entschieden, wenn Sie ein vorhandenes, gut definiertes Dateiformat lesen oder ein vorhandenes Netzwerkprotokoll implementieren müssen.

Sobald Sie das Datenformat kennen, anstatt z. B. eine int-Variable direkt auszugeben, macht Ihr Code Folgendes:

uint32_t i = ...;
uint8_t buf[4];
buf[0] = (i&0xff000000) >> 24;
buf[1] = (i&0x00ff0000) >> 16;
buf[2] = (i&0x0000ff00) >> 8;
buf[3] = (i&0x000000ff);
write(fd, buf, sizeof buf);

Wir haben jetzt das höchstwertige Byte ausgewählt und es als erstes Byte in einem Puffer platziert, und das niedrigstwertige Byte am Ende des Puffers platziert. Diese Ganzzahl wird im Big-Endian-Format in dargestellt bufunabhängig vom Endian des Hosts – also ist dieser Code Endian-agnostisch.

Der Verbraucher dieser Daten muss wissen, dass die Daten in einem Big-Endian-Format dargestellt werden. Und unabhängig davon, auf welchem ​​Host das Programm läuft, würde dieser Code diese Daten problemlos lesen:

uint32_t i;
uint8_t buf[4];
read(fd, buf, sizeof buf);
i  = (uint32_t)buf[0] << 24;
i |= (uint32_t)buf[1] << 16;
i |= (uint32_t)buf[2] << 8;
i |= (uint32_t)buf[3];

Umgekehrt, wenn die Daten, die Sie lesen müssen, bekanntermaßen im Little-Endian-Format vorliegen, würde der agnostische Endianess-Code einfach ausreichen

uint32_t i ;
uint8_t buf[4];
read(fd, buf, sizeof buf);
i  = (uint32_t)buf[3] << 24;
i |= (uint32_t)buf[2] << 16;
i |= (uint32_t)buf[1] << 8;
i |= (uint32_t)buf[0];

Sie können einige nette Inline-Funktionen oder Makros erstellen, um alle benötigten 2,4,8-Byte-Ganzzahltypen zu verpacken und zu entpacken, und wenn Sie diese verwenden und sich um das Datenformat und nicht um das Endian des Prozessors kümmern, auf dem Sie laufen, wird Ihr Code dies tun hängt nicht von der Endianess ab, auf der es läuft.

Dies ist mehr Code als viele andere Lösungen. Ich muss noch ein Programm schreiben, bei dem diese zusätzliche Arbeit einen bedeutenden Einfluss auf die Leistung hatte, selbst wenn mehr als 1 Gbit / s an Daten verschoben werden.

Es vermeidet auch einen falsch ausgerichteten Speicherzugriff, den Sie leicht mit einem Ansatz von zB erhalten können

uint32_t i;
uint8_t buf[4];
read(fd, buf, sizeof buf);
i = ntohl(*(uint32_t)buf));

Dies kann bestenfalls zu einem Leistungseinbruch führen (bei einigen unbedeutend, bei anderen in vielen, vielen Größenordnungen) und schlimmstenfalls zu einem Absturz auf Plattformen, die keinen nicht ausgerichteten Zugriff auf Ganzzahlen ermöglichen.

Benutzeravatar von Pubby
Kneipe

Dies könnte ein guter Artikel für Sie sein, den Sie lesen sollten: Der Byte-Order-Irrtum

Die Byte-Reihenfolge des Computers spielt überhaupt keine Rolle, außer für Compiler-Autoren und dergleichen, die sich um die Zuweisung von Speicherbytes kümmern, die Registerstücken zugeordnet sind. Wahrscheinlich sind Sie kein Compiler-Schreiber, also sollte Ihnen die Byte-Reihenfolge des Computers keine Rolle spielen.

Beachten Sie den Ausdruck “Byte-Reihenfolge des Computers”. Was zählt, ist die Byte-Reihenfolge eines peripheren oder codierten Datenstroms, aber – und das ist der entscheidende Punkt – die Byte-Reihenfolge des Computers, der die Verarbeitung durchführt, ist für die Verarbeitung der Daten selbst irrelevant. Wenn der Datenstrom Werte mit der Byte-Reihenfolge B codiert, sollte sich der Algorithmus zum Decodieren des Werts auf dem Computer mit der Byte-Reihenfolge C um B drehen, nicht um die Beziehung zwischen B und C.

  • Nun, wenn ich das richtig verstanden habe, zeigt der Typ ein Problem und eine Lösung, die er später als falsch bezeichnet. Was wäre da die bessere Lösung?

    – d33tah

    21. Dezember 2012 um 17:42 Uhr

  • Dies sollte eher ein Kommentar als eine Antwort sein.

    – nhahtdh

    21. Dezember 2012 um 17:45 Uhr

  • der Kerl versuch dir das zu erklären nicht Achten Sie bei der Verarbeitung von Daten auf die Endianess des Hosts: Definieren Sie einfach die Endianess der Daten und verarbeiten Sie sie entsprechend.

    – Yann Droneaud

    21. Dezember 2012 um 17:46 Uhr

  • @nhahtdh Ich würde normalerweise zustimmen, außer dass das OP erwähnte, dass er vor dem Posten nach Artikeln suchte, also ging ich davon aus, dass das Posten eines Artikels in Ordnung wäre.

    – Kneipe

    21. Dezember 2012 um 17:49 Uhr

  • @Pubby: Es ist besser, eine Art Zusammenfassung zu geben, falls der Link stirbt.

    – nhahtdh

    21. Dezember 2012 um 17:59 Uhr

Mehrere Antworten haben File IO behandelt, was sicherlich das häufigste Endian-Problem ist. Ich greife einen noch nicht Erwähnten auf: Gewerkschaften.

Die folgende Vereinigung ist ein gängiges Werkzeug in der SIMD/SSE-Programmierung und ist es auch nicht Endian-freundlich:

union uint128_t {
    _m128i      dq;
    uint64_t    dd[2];
    uint32_t    dw[4];
    uint16_t    dh[8];
    uint8_t     db[16];
};

Jeder Code, der auf die dd/dw/dh/db-Formulare zugreift, tut dies auf Endian-spezifische Weise. Auf 32-Bit-CPUs ist es auch üblich, einfachere Vereinigungen zu sehen, die es ermöglichen, 64-Bit-Arithmetik leichter in 32-Bit-Teile zu zerlegen:

union u64_parts {
    uint64_t    dd;
    uint32_t    dw[2];
};

Da es in diesem Anwendungsfall selten (wenn überhaupt) vorkommt, dass Sie über jedes Element der Union iterieren möchten, ziehe ich es vor, solche Unions wie folgt zu schreiben:

union u64_parts {
    uint64_t dd;
    struct {
#ifdef BIG_ENDIAN
        uint32_t dw2, dw1;
#else
        uint32_t dw1, dw2;
#endif
    }
};

Das Ergebnis ist ein implizites Endian-Swapping für jeden Code, der direkt auf dw1/dw2 zugreift. Der gleiche Entwurfsansatz kann auch für den obigen 128-Bit-SIMD-Datentyp verwendet werden, obwohl er am Ende erheblich ausführlicher ist.

Haftungsausschluss: Union Use ist oft verpönt wegen der losen Standarddefinitionen in Bezug auf Strukturpolsterung und -ausrichtung. Ich finde Gewerkschaften sehr nützlich und habe sie ausgiebig verwendet, und ich bin seit sehr langer Zeit (über 15 Jahre) nicht mehr auf Kompatibilitätsprobleme gestoßen. Union Padding/Alignment verhält sich für jeden aktuellen Compiler, der auf x86, ARM oder PowerPC abzielt, wie erwartet und konsistent.

Innerhalb Ihres Codes können Sie ihn ziemlich ignorieren – alles hebt sich auf.

Beim Lesen/Schreiben von Daten auf der Festplatte oder im Netzwerk htons

  • Ignorieren, wenn ich beispielsweise XTEA/RSA mache? Das hat bei mir nicht so gut geklappt.

    – d33tah

    21. Dezember 2012 um 17:40 Uhr

Benutzeravatar von Mats Petersson
Matt Petersson

Dies ist eindeutig ein ziemlich kontroverses Thema.

Der allgemeine Ansatz besteht darin, Ihre Anwendung so zu entwerfen, dass Sie sich nur um die Bytereihenfolge in einem kleinen Teil kümmern: den Eingabe- und Ausgabeabschnitten des Codes.

Überall sonst sollten Sie die native Byte-Reihenfolge verwenden.

Beachten Sie, dass, obwohl die meisten Maschinen dies auf die gleiche Weise tun, es nicht garantiert ist, dass Gleitkomma- und Integer-Daten auf die gleiche Weise gespeichert werden. Um also absolut sicher zu sein, dass die Dinge richtig funktionieren, müssen Sie nicht nur die Größe kennen, sondern auch, ob dies der Fall ist Ganzzahl oder Fließkommazahl.

Die andere Alternative besteht darin, Daten nur im Textformat zu konsumieren und zu produzieren. Dies ist wahrscheinlich fast genauso einfach zu implementieren, und es sei denn, Sie haben eine wirklich hohe Datenrate in/aus der Anwendung mit sehr wenig Verarbeitung, ist es wahrscheinlich nur ein sehr geringer Leistungsunterschied. Und mit dem Vorteil (für einige), dass Sie die Ein- und Ausgabedaten in einem Texteditor lesen können, anstatt zu versuchen, den Wert der Bytes 51213498-51213501 in der Ausgabe tatsächlich zu entschlüsseln, wenn Sie etwas falsch gemacht haben der Code.

  • Ignorieren, wenn ich beispielsweise XTEA/RSA mache? Das hat bei mir nicht so gut geklappt.

    – d33tah

    21. Dezember 2012 um 17:40 Uhr

Benutzeravatar der Community
Gemeinschaft

Wenn Sie zwischen einem 2,4- oder 8-Byte-Ganzzahltyp und einem Byte-indizierten Array (oder umgekehrt) neu interpretieren müssen, müssen Sie die Endianness kennen.

Dies tritt häufig bei der Implementierung von kryptografischen Algorithmen, Serialisierungsanwendungen (wie Netzwerkprotokollen, Dateisystemen oder Datenbank-Backends) und natürlich Betriebssystemkernen und -treibern auf.

Es wird normalerweise von einem Makro wie ENDIAN erkannt … irgendetwas.

Zum Beispiel:

uint32 x = ...;
uint8* p = (uint8*) &x;

p zeigt auf das High-Byte auf BE-Maschinen und auf das Low-Byte auf LE-Maschinen.

Mit den Makros können Sie schreiben:

uint32 x = ...;

#ifdef LITTLE_ENDIAN
    uint8* p = (uint8*) &x + 3;
#else // BIG_ENDIAN
    uint8* p = (uint8*) &x;
#endif

um zum Beispiel immer das High Byte zu bekommen.

Hier gibt es Möglichkeiten, das Makro zu definieren: C Makrodefinition, um Big-Endian- oder Little-Endian-Maschine zu bestimmen? wenn Ihre Toolchain sie nicht bereitstellt.

  • Endianness wird normalerweise von der Hardware festgelegt.

    – Karl Norum

    21. Dezember 2012 um 17:46 Uhr

  • Ja, und Sie müssen wissen, was es ist, wenn Sie einen 2-Byte- oder längeren Integer-Typ auseinandernehmen. Dies kommt zum Beispiel in kryptographischen Algorithmen häufig vor. Es kommt auch in der Vernetzung vor, wo die Netzwerkordnung immer BE ist.

    – Andreas Tomazos

    21. Dezember 2012 um 17:48 Uhr


  • Oh, ich verstehe – ich dachte, Sie sagten, Sie könnten Rückgeld mit einem Makro. Du meinst, es kann ein Makro geben, das dir sagt, welche Endianness du hast haben. Unabhängig davon müssen Sie es nicht wirklich wissen, da htonl(3) und Freunde tun immer das “Richtige”.

    – Karl Norum

    21. Dezember 2012 um 17:48 Uhr


  • Mit den Makros können Sie die Endianness des Zielcomputers erkennen. Es ist “BIG_ENDIAN” und “LITTLE_ENDIAN”, denke ich.

    – Andreas Tomazos

    21. Dezember 2012 um 17:49 Uhr


  • Alles gut, aber wie beantwortet das die Frage? OP weiß anscheinend, was Endianess ist ist und fragt, was man tun kann, um dadurch verursachte Probleme zu vermeiden.

    Benutzer395760

    21. Dezember 2012 um 17:52 Uhr

1393490cookie-checkWie schreibe ich Endian-agnostischen C/C++-Code?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy