Wie konvertiere ich in C ++ zwischen Big-Endian- und Little-Endian-Werten?
BEARBEITEN: Aus Gründen der Übersichtlichkeit muss ich Binärdaten (Gleitkommawerte mit doppelter Genauigkeit und 32-Bit- und 64-Bit-Ganzzahlen) von einer CPU-Architektur in eine andere übersetzen. Dies beinhaltet keine Vernetzung, daher funktionieren ntoh () und ähnliche Funktionen hier nicht.
EDIT # 2: Die Antwort, die ich akzeptiert habe, gilt direkt für Compiler, auf die ich ziele (weshalb ich sie gewählt habe). Es gibt hier jedoch andere sehr gute, tragbarere Antworten.
c++
endianness
Uhall
quelle
quelle
short swap(short x)
, wenn ich Code sehe , da er kaputt geht, wenn Sie auf eine Plattform mit einer anderen Endianness wechseln. Matthieu M hat unten die einzig richtige Antwort.Antworten:
Wenn Sie Visual C ++ verwenden, gehen Sie wie folgt vor: Sie schließen intrin.h ein und rufen die folgenden Funktionen auf:
Für 16-Bit-Nummern:
Für 32-Bit-Zahlen:
Für 64-Bit-Nummern:
8-Bit-Zahlen (Zeichen) müssen nicht konvertiert werden.
Außerdem sind diese nur für vorzeichenlose Werte definiert, die auch für vorzeichenbehaftete Ganzzahlen funktionieren.
Bei Floats und Doubles ist es schwieriger als bei einfachen Ganzzahlen, da diese möglicherweise in der Bytereihenfolge der Host-Computer liegen oder nicht. Sie können Little-Endian-Floats auf Big-Endian-Maschinen erhalten und umgekehrt.
Andere Compiler haben ähnliche Eigenschaften.
In GCC können Sie beispielsweise einige integrierte Funktionen direkt aufrufen, wie hier dokumentiert :
(keine Notwendigkeit, etwas einzuschließen). Afaik bits.h deklariert dieselbe Funktion auch nicht gcc-zentriert.
16-Bit-Swap ist nur ein bisschen drehen.
Wenn Sie die Intrinsics aufrufen, anstatt Ihre eigenen zu rollen, erhalten Sie übrigens die beste Leistung und Codedichte.
quelle
__builtin_bswapX
ist nur ab GCC-4.3 verfügbarhtonl
sindhtons
usw. Sie müssen aus dem Kontext Ihrer Situation wissen, wann die Bytes tatsächlich ausgetauscht werden sollen.htonl
undntohl
ohne sich um den Kontext zu kümmern würde beim Schreiben von portablem Code funktionieren, da die Plattform, die diese Funktionen definiert, ihn austauschen würde, wenn es sich um Little / Mid-Endian handelt, und bei Big-Endian wäre es ein No-Op. Wenn man jedoch einen Standarddateityp dekodiert, der als Little-Endian definiert ist (z. B. BMP), muss man den Kontext noch kennen und kann sich nicht nur aufhtonl
und verlassenntohl
.Einfach gesagt:
Verwendung :
swap_endian<uint32_t>(42)
.quelle
Aus dem Byte Order Fallacy von Rob Pike:
TL; DR: Machen Sie sich keine Sorgen um die native Reihenfolge Ihrer Plattform. Alles, was zählt, ist die Bytereihenfolge des Streams, aus dem Sie lesen, und Sie hoffen besser, dass er gut definiert ist.
Hinweis: In dem Kommentar wurde darauf hingewiesen, dass es ohne explizite Typkonvertierung wichtig ist,
data
ein Array vonunsigned char
oder zu seinuint8_t
. Die Verwendung vonsigned char
oderchar
(falls signiert) führtdata[x]
dazu, dass eine Ganzzahl heraufgestuft wird unddata[x] << 24
möglicherweise eine 1 in das Vorzeichenbit UB verschoben wird.quelle
Wenn Sie dies aus Gründen der Netzwerk- / Hostkompatibilität tun, sollten Sie Folgendes verwenden:
Wenn Sie dies aus einem anderen Grund tun, funktioniert eine der hier vorgestellten byte_swap-Lösungen einwandfrei.
quelle
htonl
undntohl
kann nicht zu Little Endian auf einer Big-Endian-Plattform gehen.Ich habe ein paar Vorschläge aus diesem Beitrag genommen und sie zu diesem zusammengestellt:
quelle
Das Verfahren für den Übergang von Big-Endian zu Little-Endian ist das gleiche wie für den Übergang von Little-Endian zu Big-Endian.
Hier ist ein Beispielcode:
quelle
Es gibt eine Montageanleitung namens BSWAP, die den Austausch extrem schnell für Sie erledigt . Sie können darüber lesen Sie hier .
Visual Studio, genauer gesagt die Visual C ++ - Laufzeitbibliothek, verfügt hierfür über Plattform-Intrinsics
_byteswap_ushort(), _byteswap_ulong(), and _byteswap_int64()
. Ähnliches sollte für andere Plattformen existieren, aber ich weiß nicht, wie sie heißen würden.quelle
Wir haben dies mit Vorlagen gemacht. Sie könnten so etwas tun:
quelle
Wenn Sie dies tun, um Daten zwischen verschiedenen Plattformen zu übertragen, sehen Sie sich die Funktionen ntoh und hton an.
quelle
Genauso wie in C:
Sie können auch einen Vektor mit vorzeichenlosen Zeichen deklarieren, den Eingabewert darin speichern, die Bytes in einen anderen Vektor umkehren und die Bytes auswendig lernen. Dies dauert jedoch um Größenordnungen länger als das Bit-Twiddling, insbesondere bei 64-Bit-Werten.
quelle
Auf den meisten POSIX-Systemen (da dies nicht im POSIX-Standard enthalten ist) gibt es die Datei endian.h, mit der bestimmt werden kann, welche Codierung Ihr System verwendet. Von dort ist es ungefähr so:
Dies vertauscht die Reihenfolge (von Big Endian zu Little Endian):
Wenn Sie die Nummer 0xDEADBEEF haben (auf einem Little-Endian-System, das als 0xEFBEADDE gespeichert ist), ist ptr [0] 0xEF, ptr [1] ist 0xBE usw.
Wenn Sie es jedoch für die Vernetzung verwenden möchten, sind htons, htonl und htonll (und ihre Umkehrungen ntohs, ntohl und ntohll) hilfreich, um von der Hostreihenfolge in die Netzwerkreihenfolge zu konvertieren.
quelle
htonl
und Freunde verwenden, unabhängig davon, ob der Anwendungsfall etwas mit dem Netzwerk zu tun hat. Die Reihenfolge der Netzwerkbytes ist Big-Endian. Behandeln Sie diese Funktionen also einfach als host_to_be und be_to_host. (Hilft nicht, wenn Sie host_to_le benötigen.)Beachten Sie, dass htonl () zumindest für Windows viel langsamer ist als das eigentliche Gegenstück _byteswap_ulong (). Ersteres ist ein DLL-Bibliotheksaufruf in ws2_32.dll, letzteres ist eine BSWAP-Assemblyanweisung. Wenn Sie plattformabhängigen Code schreiben, verwenden Sie daher lieber die Eigenheiten für die Geschwindigkeit:
Dies kann besonders wichtig für die PNG-Bildverarbeitung sein, bei der alle Ganzzahlen in Big Endian mit der Erklärung "Man kann htonl () ..." {zum Verlangsamen typischer Windows-Programme verwenden, wenn Sie nicht vorbereitet sind} gespeichert werden.
quelle
Die meisten Plattformen verfügen über eine Systemheaderdatei, die effiziente Byteswap-Funktionen bietet. Unter Linux ist es in
<endian.h>
. Sie können es schön in C ++ verpacken:Ausgabe:
quelle
Ich mag dieses, nur für Stil :-)
quelle
char[]
"Fehler: Unvollständiger Typ ist nicht zulässig"Im Ernst ... ich verstehe nicht, warum alle Lösungen so kompliziert sind ! Wie wäre es mit der einfachsten, allgemeinsten Vorlagenfunktion, die jede Art von Größe unter allen Umständen in jedem Betriebssystem austauscht?
Es ist die magische Kraft von C und C ++ zusammen! Tauschen Sie einfach die ursprüngliche Variable Zeichen für Zeichen aus.
Punkt 1 : Keine Operatoren: Denken Sie daran, dass ich den einfachen Zuweisungsoperator "=" nicht verwendet habe, da einige Objekte durcheinander gebracht werden, wenn die Endianness umgedreht wird und der Kopierkonstruktor (oder Zuweisungsoperator) nicht funktioniert. Daher ist es zuverlässiger, sie char für char zu kopieren.
Punkt 2 : Beachten Sie Ausrichtungsprobleme: Beachten Sie, dass wir in und aus einem Array kopieren. Dies ist die richtige Vorgehensweise, da der C ++ - Compiler nicht garantiert, dass wir auf nicht ausgerichteten Speicher zugreifen können (diese Antwort wurde vom Original aktualisiert Formular dafür). Wenn Sie beispielsweise zuweisen
uint64_t
, kann Ihr Compiler nicht garantieren, dass Sie auf das 3. Byte davon als zugreifen könnenuint8_t
. Daher ist es richtig, dies in ein char-Array zu kopieren, auszutauschen und dann zurück zu kopieren (also neinreinterpret_cast
). Beachten Sie, dass Compiler meistens intelligent genug sind, um das, was Sie getan haben, wieder in ein zu konvertieren,reinterpret_cast
wenn sie unabhängig von der Ausrichtung auf einzelne Bytes zugreifen können.So verwenden Sie diese Funktion :
und nun
x
ist anders in endianness.quelle
new
/delete
, um einen Puffer dafür zuzuweisen?!?sizeof(var)
ist eine Konstante zur Kompilierungszeit, also können Sie dies tunchar varSwapped[sizeof(var)]
. Oder Sie könnten an Ortchar *p = reinterpret_cast<char*>(&var)
und Stelle tauschen.for(size_t i = 0 ; i < sizeof(var) ; i++)
anstelle von astatic_cast<long>
. (Oder tatsächlich verwendet der In-Place-Tausch einen aufsteigenden und einen absteigenden,char*
so dass er sowieso verschwindet).Ich habe diesen Code, mit dem ich von HOST_ENDIAN_ORDER (was auch immer es ist) nach LITTLE_ENDIAN_ORDER oder BIG_ENDIAN_ORDER konvertieren kann. Ich verwende eine Vorlage. Wenn ich also versuche, von HOST_ENDIAN_ORDER nach LITTLE_ENDIAN_ORDER zu konvertieren, und diese für den Computer, für den ich kompiliere, identisch sind, wird kein Code generiert.
Hier ist der Code mit einigen Kommentaren:
quelle
Wenn eine Big-Endian-32-Bit-Ganzzahl ohne Vorzeichen wie 0xAABBCCDD aussieht, was 2864434397 entspricht, sieht dieselbe 32-Bit-Ganzzahl ohne Vorzeichen wie 0xDDCCBBAA auf einem Little-Endian-Prozessor aus, der ebenfalls 2864434397 entspricht.
Wenn ein Big-Endian-16-Bit-Short ohne Vorzeichen wie 0xAABB aussieht, was 43707 entspricht, dann sieht derselbe 16-Bit-Short ohne Vorzeichen wie 0xBBAA auf einem Little-Endian-Prozessor aus, der ebenfalls 43707 entspricht.
Hier sind einige praktische # Define-Funktionen zum Wechseln von Bytes von Little-Endian zu Big-Endian und umgekehrt ->
quelle
Hier ist eine verallgemeinerte Version, die ich mir ausgedacht habe, um einen Wert auszutauschen. Die anderen Vorschläge wären besser, wenn die Leistung ein Problem darstellt.
Haftungsausschluss: Ich habe noch nicht versucht, dies zu kompilieren oder zu testen.
quelle
Wenn Sie das übliche Muster zum Umkehren der Bitreihenfolge in einem Wort verwenden und den Teil auswählen, der die Bits innerhalb jedes Bytes umkehrt, bleibt etwas übrig, das nur die Bytes innerhalb eines Wortes umkehrt. Für 64-Bit:
Der Compiler sollte die überflüssigen Bitmaskierungsoperationen bereinigen (ich habe sie belassen, um das Muster hervorzuheben), aber wenn dies nicht der Fall ist, können Sie die erste Zeile folgendermaßen umschreiben:
Dies sollte sich bei den meisten Architekturen normalerweise auf einen einzigen Drehbefehl vereinfachen (wobei zu ignorieren ist, dass der gesamte Vorgang wahrscheinlich ein Befehl ist).
Auf einem RISC-Prozessor können die großen, komplizierten Konstanten dem Compiler Schwierigkeiten bereiten. Sie können jedoch trivial jede der Konstanten aus der vorherigen berechnen. Wie so:
Wenn Sie möchten, können Sie dies als Schleife schreiben. Es wird nicht effizient sein, sondern nur zum Spaß:
Der Vollständigkeit halber hier die vereinfachte 32-Bit-Version des ersten Formulars:
quelle
Ich dachte nur, ich hätte hier meine eigene Lösung hinzugefügt, da ich sie nirgendwo gesehen habe. Es ist eine kleine und tragbare C ++ - Vorlagenfunktion, die nur Bitoperationen verwendet.
quelle
Ich bin wirklich überrascht, dass niemand die Funktionen htobeXX und betohXX erwähnt hat. Sie sind in endian.h definiert und den Netzwerkfunktionen htonXX sehr ähnlich.
quelle
Mit den folgenden Codes können Sie einfach zwischen BigEndian und LittleEndian wechseln
quelle
Ich habe kürzlich ein Makro geschrieben, um dies in C zu tun, aber es ist auch in C ++ gültig:
Es akzeptiert jeden Typ und kehrt die Bytes im übergebenen Argument um. Beispielverwendungen:
Welche Drucke:
Das Obige ist perfekt kopierbar / einfügbar, aber hier ist viel los, also werde ich Stück für Stück aufschlüsseln, wie es funktioniert:
Das erste Bemerkenswerte ist, dass das gesamte Makro in einem
do while(0)
Block eingeschlossen ist. Dies ist eine gängige Redewendung , um die normale Verwendung von Semikolons nach dem Makro zu ermöglichen.Als nächstes wird eine Variable verwendet, die
REVERSE_BYTES
alsfor
Zähler der Schleife bezeichnet wird. Der Name des Makros selbst wird als Variablenname verwendet, um sicherzustellen, dass er nicht mit anderen Symbolen in Konflikt gerät, die möglicherweise überall dort verwendet werden, wo das Makro verwendet wird. Da der Name in der Makroerweiterung verwendet wird, wird er nicht erneut erweitert, wenn er hier als Variablenname verwendet wird.Innerhalb der
for
Schleife werden zwei Bytes referenziert und XOR ausgetauscht (daher ist kein temporärer Variablenname erforderlich):__VA_ARGS__
stellt alles dar, was dem Makro gegeben wurde, und wird verwendet, um die Flexibilität dessen zu erhöhen, was übergeben werden kann (wenn auch nicht viel). Die Adresse dieses Arguments wird dann genommen und in einenunsigned char
Zeiger umgewandelt, um das Austauschen seiner Bytes über ein Array zu ermöglichen[]
Subskription zu ermöglichen.Der letzte besondere Punkt ist das Fehlen von
{}
Zahnspangen. Sie sind nicht erforderlich, da alle Schritte in jedem Swap mit dem Komma-Operator verknüpft sind , was sie zu einer Anweisung macht.Schließlich ist anzumerken, dass dies nicht der ideale Ansatz ist, wenn Geschwindigkeit oberste Priorität hat. Wenn dies ein wichtiger Faktor ist, sind einige der typspezifischen Makros oder plattformspezifischen Anweisungen, auf die in anderen Antworten verwiesen wird, wahrscheinlich die bessere Option. Dieser Ansatz ist jedoch auf alle Typen, alle wichtigen Plattformen sowie auf die Sprachen C und C ++ portierbar.
quelle
__VA_ARGS__
?Wow, ich konnte einige der Antworten, die ich hier gelesen habe, nicht glauben. Es gibt tatsächlich eine Anweisung in der Montage, die dies schneller als alles andere erledigt. bswap. Sie könnten einfach eine Funktion wie diese schreiben ...
Es ist VIEL schneller als die vorgeschlagenen Eigenschaften. Ich habe sie zerlegt und geschaut. Die obige Funktion hat keinen Prolog / Epilog, hat also praktisch überhaupt keinen Overhead.
16 Bit zu machen ist genauso einfach, mit der Ausnahme, dass Sie xchg al verwenden würden, ah. bswap funktioniert nur mit 32-Bit-Registern.
64-Bit ist etwas kniffliger, aber nicht übermäßig. Viel besser als alle oben genannten Beispiele mit Schleifen und Vorlagen usw.
Hier gibt es einige Einschränkungen ... Erstens ist bswap nur auf 80x486-CPUs und höher verfügbar. Plant jemand, es auf einem 386 laufen zu lassen?!? Wenn ja, können Sie bswap immer noch durch ...
Außerdem ist die Inline-Assembly nur in x86-Code in Visual Studio verfügbar. Eine nackte Funktion kann nicht ausgekleidet werden und ist auch in x64-Builds nicht verfügbar. In diesem Fall müssen Sie die Compiler-Eigenschaften verwenden.
quelle
_byteswap_ulong
und_uint64
(z. B. in der akzeptierten Antwort) beide kompilieren, um diebswap
Anweisung zu verwenden. Ich wäre überrascht, aber interessiert zu wissen, ob dieser Asm so viel schneller ist, da nur der Prolog / Epilog weggelassen wird - haben Sie ihn bewertet?Tragbare Technik zur Implementierung optimiererfreundlicher, nicht ausgerichteter, nicht vorhandener Endian-Accessoren. Sie arbeiten mit jedem Compiler, jeder Grenzausrichtung und jeder Bytereihenfolge. Diese nicht ausgerichteten Routinen werden je nach nativem Endian und Ausrichtung ergänzt oder diskutiert. Teilweise Auflistung, aber Sie bekommen die Idee. BO * sind konstante Werte basierend auf der nativen Bytereihenfolge.
Diese Typedefs haben den Vorteil, dass sie Compilerfehler auslösen, wenn sie nicht mit Accessoren verwendet werden, wodurch vergessene Accessor-Fehler verringert werden.
quelle
So lesen Sie ein im IEEE 754 64-Bit-Format gespeichertes Double, auch wenn Ihr Host-Computer ein anderes System verwendet.
Für den Rest der Funktionssuite, einschließlich der Schreib- und Ganzzahlroutinen, siehe mein Github-Projekt
https://github.com/MalcolmMcLean/ieee754
quelle
Das Austauschen von Bytes mit dem alten 3-Schritt-xor-Trick um einen Drehpunkt in einer Vorlagenfunktion ergibt eine flexible, schnelle O (ln2) -Lösung, für die keine Bibliothek erforderlich ist. Der Stil hier lehnt auch 1-Byte-Typen ab:
quelle
Es scheint, als wäre der sichere Weg, für jedes Wort htons zu verwenden. Also, wenn Sie haben ...
Das Obige wäre ein No-Op, wenn Sie sich auf einem Big-Endian-System befinden. Daher würde ich nach dem suchen, was Ihre Plattform als Bedingung für die Kompilierungszeit verwendet, um zu entscheiden, ob htons ein No-Op ist. Es ist schließlich O (n). Auf einem Mac wäre es so etwas wie ...
quelle
Wenn Sie C ++ 17 haben, fügen Sie diesen Header hinzu
Verwenden Sie diese Vorlagenfunktion, um die Bytes auszutauschen:
nenne es wie:
quelle
Suchen Sie nach Bit Shifting, da dies im Grunde alles ist, was Sie tun müssen, um von Little -> Big Endian zu wechseln. Dann ändern Sie abhängig von der Bitgröße, wie Sie die Bitverschiebung durchführen.
quelle