Endianness erkennen

Lesezeit: 9 Minuten

Benutzer-Avatar
Cyan

Ich versuche gerade eine zu erstellen C Quellcode, der I/O unabhängig von der Endianness des Zielsystems richtig handhabt.

Ich habe “Little Endian” als E/A-Konvention ausgewählt, was bedeutet, dass ich für eine Big-Endian-CPU Daten beim Schreiben oder Lesen konvertieren muss.

Konvertierung ist nicht das Problem. Das Problem, dem ich gegenüberstehe, besteht darin, die Endianness zu erkennen, vorzugsweise zur Kompilierzeit (da die CPU die Endianness mitten in der Ausführung nicht ändert …).

Bisher habe ich das hier verwendet:

#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
...
#else
...
#endif

Es ist als vordefiniertes GCC-Makro dokumentiert, und Visual scheint es auch zu verstehen.

Ich habe jedoch die Meldung erhalten, dass die Prüfung bei einigen big_endian-Systemen (PowerPC) fehlschlägt.

Ich suche also nach einer narrensicheren Lösung, die sicherstellt, dass Endianess unabhängig vom Compiler und Zielsystem korrekt erkannt wird. naja, die meisten zumindest…

[Edit] : Die meisten der vorgeschlagenen Lösungen beruhen auf “Laufzeittests”. Diese Tests werden unter Umständen von Compilern während der Kompilierung richtig ausgewertet und kosten daher keine wirkliche Laufzeitleistung.

Verzweigung jedoch mit einer Art << if (0) { … } else { … } >> reicht nicht. In der aktuellen Codeimplementierung Variablen und Funktionen Erklärung hängen von der big_endian-Erkennung ab. Diese können nicht mit einer if-Anweisung geändert werden.

Nun, offensichtlich gibt es einen Ausweichplan, der darin besteht, den Code neu zu schreiben …

Ich würde das lieber vermeiden, aber, naja, es sieht nach einer schwindenden Hoffnung aus…

[Edit 2] : Ich habe “Laufzeittests” getestet, indem ich den Code tiefgreifend verändert habe. Obwohl sie ihre Aufgabe korrekt erfüllen, wirken sich diese Tests auch auf die Leistung aus.

Ich hatte erwartet, dass der Compiler fehlerhafte Verzweigungen eliminieren könnte, da die Tests eine vorhersehbare Ausgabe haben. Aber leider funktioniert es nicht immer. MSVC ist ein guter Compiler und eliminiert erfolgreich fehlerhafte Verzweigungen, aber GCC hat gemischte Ergebnisse, je nach Version, Art der Tests und mit größeren Auswirkungen auf 64 Bit als auf 32 Bit.

Es ist komisch. Und es bedeutet auch, dass die Ausführung der Laufzeittests durch den Compiler nicht sichergestellt werden kann.

Bearbeiten 3 : Heutzutage verwende ich eine konstante Vereinigung zur Kompilierzeit und erwarte, dass der Compiler sie zu einem klaren Ja/Nein-Signal löst. Und es funktioniert ziemlich gut:
https://godbolt.org/g/DAafKo

  • @BoPersson – dies ist keine Erkennung der Kompilierzeit

    – MByD

    23. Januar 2012 um 21:40 Uhr

  • Die Laufzeit ist Ihre beste Wahl, aber die Kompilierzeit ist in den folgenden Antworten enthalten: 1. stackoverflow.com/a/1001373/1094175 2. stackoverflow.com/a/2100385/1094175

    – Brett McLain

    23. Januar 2012 um 21:43 Uhr

  • Einige CPUs tatsächlich kann haben unterschiedliche Endianness für verschiedene ausführbare Dateien. en.wikipedia.org/wiki/Endianness#Bi-endian_hardware

    – Bo Persson

    23. Januar 2012 um 21:47 Uhr


  • @Cyan, abgesehen von denen, die Sie erwähnt haben, gibt es keinen. Kompilieren Sie also entweder ein kleines Programm, das die Endianess erkennt, und speisen Sie das Ergebnis in Ihr Build-System ein, damit es ein Präprozessormakro definiert, oder schreiben Sie den Code so, dass er unabhängig von der Host-Endianess ist.

    – Nr

    23. Januar 2012 um 22:02 Uhr

  • Der Grund, warum Ihr präprozessorbasierter Test fehlschlagen kann (falsch positiv), ist, dass undefinierte Symbole durch ersetzt werden 0 in #if Richtlinien.

    – R.. GitHub HÖR AUF, EIS ZU HELFEN

    24. Januar 2012 um 1:23 Uhr

Benutzer-Avatar
Cyan

Wie bereits erwähnt, ist die einzige “echte” Möglichkeit, Big Endian zu erkennen, die Verwendung von Laufzeittests.

Manchmal wird jedoch ein Makro bevorzugt.

Leider habe ich keinen einzigen “Test” gefunden, um diese Situation zu erkennen, sondern eine Sammlung davon.

GCC empfiehlt zum Beispiel: __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ . Dies funktioniert jedoch nur mit den neuesten Versionen, und frühere Versionen (und andere Compiler) geben diesem Test einen falschen Wert “true”, da NULL == NULL. Sie benötigen also die vollständigere Version: defined(__BYTE_ORDER__)&&(__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)

OK, jetzt funktioniert das für den neuesten GCC, aber was ist mit anderen Compilern?

Du kannst es versuchen __BIG_ENDIAN__ oder __BIG_ENDIAN oder _BIG_ENDIAN die häufig auf Big-Endian-Compilern definiert werden.

Dadurch wird die Erkennung verbessert. Wenn Sie jedoch speziell auf PowerPC-Plattformen abzielen, können Sie einige weitere Tests hinzufügen, um die Erkennung noch weiter zu verbessern. Versuchen _ARCH_PPC oder __PPC__ oder __PPC oder PPC oder __powerpc__ oder __powerpc oder auch powerpc. Binden Sie alle diese Definitionen zusammen, und Sie haben eine ziemlich gute Chance, Big-Endian-Systeme und insbesondere PowerPC zu erkennen, unabhängig vom Compiler und seiner Version.

Zusammenfassend lässt sich sagen, dass es keine “vordefinierten Standardmakros” gibt, die garantieren, dass Big-Endian-CPUs auf allen Plattformen und Compilern erkannt werden, aber es gibt viele solcher vordefinierten Makros, die zusammengenommen eine hohe Wahrscheinlichkeit ergeben Big Endian unter den meisten Umständen korrekt zu erkennen.

  • Schreiben für andere Leute, die diese Antwort nützlich finden. gcc unterstützt __BYTE_ORDER__ ab ca. 4,6 und Klang ab 3,2

    – Andrej

    10. Juli 2019 um 5:14 Uhr


  • Du kannst schreiben static_assert(__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__); die auf GCC funktioniert und ansonsten auf Compilern fehlschlägt, die diese nicht definieren.

    – Brent

    26. Mai um 19:56 Uhr

Benutzer-Avatar
Matteo Italien

Zur Kompilierzeit in C können Sie nicht viel mehr tun, als dem Präprozessor zu vertrauen #defines, und es gibt keine Standardlösungen, da sich der C-Standard nicht mit Endianness befasst.

Dennoch könnten Sie eine Zusicherung hinzufügen, die zur Laufzeit beim Start des Programms ausgeführt wird, um sicherzustellen, dass die beim Kompilieren getroffene Annahme wahr ist:

inline int IsBigEndian()
{
    int i=1;
    return ! *((char *)&i);
}

/* ... */

#ifdef COMPILED_FOR_BIG_ENDIAN
assert(IsBigEndian());
#elif COMPILED_FOR_LITTLE_ENDIAN
assert(!IsBigEndian());
#else
#error "No endianness macro defined"
#endif

(wo COMPILED_FOR_BIG_ENDIAN und COMPILED_FOR_LITTLE_ENDIAN sind Makros #defined zuvor gemäß Ihren Präprozessor-Endianness-Prüfungen)

  • Der Wert eines anderen Union-Mitglieds als dem letzten, in dem gespeichert wurde, ist ein nicht spezifiziertes Verhalten in C.

    – au

    23. Januar 2012 um 21:53 Uhr

  • @ouah: Der C-Standard weiß nichts über Endianness, also verlassen wir bereits die Standarddomäne und arbeiten an implementierungsspezifischem Verhalten (und ich glaube nicht, dass Sie jemals einen Compiler finden werden, der implementiert unions anders oder ein Optimierer, der mit ihnen herumspielt). Ich stimme jedoch zu, dass die andere “klassische Methode” (Umwandlung des Zeigers auf char *) weist aufgrund der Ausnahmen von den Aliasing-Regeln keine UB-Probleme auf.

    – Matteo Italien

    23. Januar 2012 um 22:00 Uhr


  • @ouah: Außerdem erwähnt §6.7.2.1 UB nicht, es heißt nur: “Der Wert von höchstens einem der Mitglieder kann jederzeit in einem Union-Objekt gespeichert werden”; Außerdem wage ich zu sagen, dass §6.7.2.1 §14 implizit die Verwendung von erlaubt unions als Ersatz für diese Umwandlung, da “Ein Zeiger auf ein Union-Objekt, passend konvertiert, auf jedes seiner Mitglieder zeigt […] und umgekehrt.”. Also, &u.i = &u = &u.c (mit den entsprechenden Besetzungen), also u.c[0] = (*(&u.c))[0]=*((char *)&u.i)die genauso legal ist wie die “andere Methode”.

    – Matteo Italien

    23. Januar 2012 um 22:13 Uhr

  • In C99, Anhang J (nicht normativ) „J.1 Nicht spezifiziertes Verhalten. und 6.2.6.1p7 sagt: “Wenn ein Wert in einem Mitglied eines Objekts vom Union-Typ gespeichert wird, nehmen die Bytes der Objektdarstellung, die diesem Mitglied nicht entsprechen, aber anderen Mitgliedern entsprechen, nicht spezifizierte Werte an.”

    – au

    23. Januar 2012 um 22:20 Uhr

  • @ouah: Das erste wird gelöst, indem ich an §6.7.2.1 ¶14 arbeite, wie ich bereits zuvor geschrieben habe (es ist immer noch ein nicht spezifiziertes Verhalten, aber genau wie es die Besetzung ist – und hey, dieser Code ist da, um genau zu verstehen, wie der Compiler implementiert dieses “nicht spezifizierte Verhalten”). Ihr zweites Zitat ist irrelevant, da die beiden Mitglieder in meiner Gewerkschaft gleich groß sind, also beide Mitglieder die “vollständig ausfüllen”. union (und dies würde auch dann noch gelten, wenn ich eine Single deklariert hätte charweil das größte Mitglied zuerst gespeichert wird).

    – Matteo Italien

    23. Januar 2012 um 22:28 Uhr


Anstatt nach einer Prüfung zur Kompilierzeit zu suchen, warum nicht einfach die Big-Endian-Reihenfolge verwenden (die als “Netzwerkordnung” von vielen) und verwenden Sie die htons/htonl/ntohs/ntohl Funktionen, die von den meisten UNIX-Systemen und Windows bereitgestellt werden. Sie sind bereits für die Arbeit definiert, die Sie zu erledigen versuchen. Warum das Rad neu erfinden?

  • Guter Punkt. Leider kann ich diese Konvention jetzt nicht ändern, da der Code schon seit geraumer Zeit im Einsatz ist und kompatibel zu bestehenden Benutzerdaten bleiben muss.

    – Cyan

    23. Januar 2012 um 22:28 Uhr

  • @ Cyan – Ah. In diesem Fall müssen Sie eine Build-Time-Prüfung mit etwas wie autoconf durchführen, um das Makro für Sie zu definieren, oder sich mit einer Laufzeitlösung zufrieden geben.

    – Chris Lutz

    23. Januar 2012 um 22:32 Uhr

  • Dies funktioniert nur, wenn keine 64-Bit-Datentypen vorhanden sind. Zumindest unter Linux gibt htonl a zurück uint32_tnicht ein unsigned long, also sollte es selbst auf 64-Bit-Plattformen mit 32-Bit-Werten arbeiten. Das ist normalerweise das gewünschte Verhalten, damit die Funktion mit vorhandenem Netzwerkcode korrekt funktioniert.

    – Brian McFarland

    23. Januar 2012 um 22:44 Uhr

  • @BrianMcFarland – Ja, als ich die Manpages durchgesehen habe, dachte ich: “Ich erinnere mich, dass diese Funktionsfamilie viel nützlicher war, als ich sie das letzte Mal gesehen habe.” Ich nehme an, dass die Leute normalerweise ihre eigenen plattformabhängigen Wrapper implementieren müssen. Ich frage mich, ob irgendwelche Compiler verschiedene Runtime-Endian-Check-Idiome in Kompilierzeitkonstanten optimieren, um Codeausführungspfade zu reduzieren?

    – Chris Lutz

    24. Januar 2012 um 0:07 Uhr

Versuchen Sie etwas wie:

if(*(char *)(int[]){1}) {
    /* little endian code */
} else {
    /* big endian code */
}

und sehen Sie, ob Ihr Compiler es zur Kompilierzeit auflöst. Wenn nicht, haben Sie vielleicht mehr Glück, wenn Sie dasselbe mit einer Gewerkschaft tun. Eigentlich mag ich es, Makros mit Unions zu definieren, die zu 0,1 oder 1,0 (jeweils) ausgewertet werden, damit ich nur Dinge wie den Zugriff tun kann buf[HI] und buf[LO].

Ungeachtet der vom Compiler definierten Makros gibt es meiner Meinung nach keine Möglichkeit, dies zur Kompilierzeit zu erkennen, da die Bestimmung der Endianness einer Architektur die Analyse der Art und Weise erfordert, in der sie Daten im Speicher speichert.

Hier ist eine Funktion, die genau das tut:

bool IsLittleEndian () {

    int i=1;

    return (int)*((unsigned char *)&i)==1;

}

Benutzer-Avatar
Vorlagentypdef

Wie andere darauf hingewiesen haben, gibt es keine portable Möglichkeit, zur Kompilierungszeit auf Endianness zu prüfen. Eine Möglichkeit wäre jedoch die Verwendung von autoconf Tool als Teil Ihres Build-Skripts, um zu erkennen, ob das System Big-Endian oder Little-Endian ist, und dann das zu verwenden AC_C_BIGENDIAN Makro, das diese Informationen enthält. In gewissem Sinne baut dies ein Programm auf, das zur Laufzeit erkennt, ob das System Big-Endian oder Little-Endian ist, und dann über diese Programmausgabeinformationen verfügt, die dann statisch vom Hauptquellcode verwendet werden können.

Hoffe das hilft!

Benutzer-Avatar
Geremia

Das kommt von S. 45 von Zeiger in C:

#include <stdio.h>
#define BIG_ENDIAN 0
#define LITTLE_ENDIAN 1

int endian()
{
   short int word = 0x0001;
   char *byte = (char *) &word;
   return (byte[0] ? LITTLE_ENDIAN : BIG_ENDIAN);
}

int main(int argc, char* argv[])
{
   int value;
   value = endian();
   if (value == 1)
      printf("The machine is Little Endian\n");
   else
      printf("The machine is Big Endian\n");
   return 0;
}

1354210cookie-checkEndianness erkennen

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

Privacy policy