Wie konvertiere ich in C ++ zwischen Big-Endian- und Little-Endian-Werten?

196

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.

Uhall
quelle
21
ntoh hton wird gut funktionieren, auch wenn es nichts mit Networking zu tun hat.
Ben Collins
2
Der beste Weg, um mit Endianness im Allgemeinen umzugehen, besteht darin, sicherzustellen, dass der Code sowohl auf Little- als auch auf Big-Endian-Host-Computern ausgeführt wird. Wenn das funktioniert, haben Sie es wahrscheinlich richtig gemacht. Angenommen, Sie sind auf x86 / be, ist in der Praxis gefährlich.
Jakobengblom2
10
hton ntoh funktioniert nicht, wenn die Maschine Big-Endian ist, da der Fragesteller die Konvertierung ausdrücklich durchführen möchte.
Fabspro
6
@ jakobengblom2 ist die einzige Person, die dies erwähnt. Fast alle Beispiele auf dieser Seite verwenden Konzepte wie "Swap" -Bytes, anstatt dies unabhängig von der zugrunde liegenden Endianness zu tun. Wenn Sie mit externen Dateiformaten arbeiten (die eine genau definierte Endianness haben), ist es am portabelsten, die externen Daten als Byte-Stream zu behandeln und den Byte-Stream in und aus den nativen Ganzzahlen zu konvertieren. Ich erschrecke jedes Mal 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.
Mark Lakata
3
Sie denken über das Problem völlig falsch nach. Die Aufgabe ist nicht "Wie konvertiere ich zwischen Big-Endian- und Little-Endian-Werten". Die Aufgabe lautet: "Wie konvertiere ich Gleitkomma- und Ganzzahlwerte in einem bestimmten Format in das native Format meiner Plattform?" Wenn Sie es richtig machen, kann das native Format Big Endian, Little Endian, Mixed Endian oder Ternary für alle Ihre Code-Sorgen sein.
David Schwartz

Antworten:

166

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:

unsigned short _byteswap_ushort(unsigned short value);

Für 32-Bit-Zahlen:

unsigned long _byteswap_ulong(unsigned long value);

Für 64-Bit-Nummern:

unsigned __int64 _byteswap_uint64(unsigned __int64 value);

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 :

uint32_t __builtin_bswap32 (uint32_t x)
uint64_t __builtin_bswap64 (uint64_t x)

(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.

Nils Pipenbrinck
quelle
11
Mit GCC könnte ich verwenden: #include <byteswap.h> int32_t bswap_32 (int32_t x) int64_t bswap_64 (int64_t x)
jmanning2k
5
__builtin_bswapXist nur ab GCC-4.3 verfügbar
Matt Joiner
20
Es ist auch erwähnenswert, dass diese intrinsischen / immer / Swap-Bytes nicht ähnlich htonlsind htonsusw. Sie müssen aus dem Kontext Ihrer Situation wissen, wann die Bytes tatsächlich ausgetauscht werden sollen.
Brian Vandenberg
8
@ Jason, weil 8-Bit-Zahlen in Big und Little Endian gleich sind. :-)
Nils Pipenbrinck
2
@BrianVandenberg Richtig; Das Verwenden htonlund ntohlohne 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 auf htonlund verlassen ntohl.
Legends2k
86

Einfach gesagt:

#include <climits>

template <typename T>
T swap_endian(T u)
{
    static_assert (CHAR_BIT == 8, "CHAR_BIT != 8");

    union
    {
        T u;
        unsigned char u8[sizeof(T)];
    } source, dest;

    source.u = u;

    for (size_t k = 0; k < sizeof(T); k++)
        dest.u8[k] = source.u8[sizeof(T) - k - 1];

    return dest.u;
}

Verwendung : swap_endian<uint32_t>(42).

Alexandre C.
quelle
3
Habe eine Gegenstimme. Ich habe gerade Uchars verwendet und 4 zu 1, 3 zu 2, 2 zu 3 und 1 zu 4 zugewiesen, aber dies ist flexibler, wenn Sie unterschiedliche Größen haben. 6 Uhren auf einem Pentium IIRC der 1. Generation. BSWAP ist 1 Uhr, aber plattformspezifisch.
2
@RocketRoy: Ja, und wenn sich herausstellt, dass Geschwindigkeit ein Problem darstellt, ist es sehr einfach, Überladungen mit plattform- und typspezifischen Intrics zu schreiben.
Alexandre C.
3
@MihaiTodor: Diese Verwendung von Gewerkschaften für die Typumwandlung durch ein Array von Zeichen ist vom Standard ausdrücklich zulässig. Siehe z. diese Frage .
Alexandre C.
4
@AlexandreC. Nicht im C ++ - Standard - nur in C. In C ++ (was dieser Code ist) ist dieser Code ein undefiniertes Verhalten.
Rapptz
4
@Rapptz: 3.10 scheint klar zu sein: "Wenn ein Programm versucht, über einen anderen Wert als einen der folgenden Typen auf den gespeicherten Wert eines Objekts zuzugreifen, ist das Verhalten undefiniert: [...] ein Zeichen oder ein Zeichen ohne Vorzeichen. " Vielleicht fehlt mir hier etwas, aber mir war ziemlich klar, dass der Zugriff auf einen beliebigen Typ über Zeichenzeiger ausdrücklich erlaubt war.
Alexandre C.
75

Aus dem Byte Order Fallacy von Rob Pike:

Angenommen, Ihr Datenstrom hat eine Little-Endian-codierte 32-Bit-Ganzzahl. So extrahieren Sie es (unter der Annahme vorzeichenloser Bytes):

i = (data[0]<<0) | (data[1]<<8) | (data[2]<<16) | (data[3]<<24);

Wenn es Big-Endian ist, können Sie es folgendermaßen extrahieren:

i = (data[3]<<0) | (data[2]<<8) | (data[1]<<16) | (data[0]<<24);

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, dataein Array von unsigned charoder zu sein uint8_t. Die Verwendung von signed charoder char(falls signiert) führt data[x]dazu, dass eine Ganzzahl heraufgestuft wird und data[x] << 24möglicherweise eine 1 in das Vorzeichenbit UB verschoben wird.

Matthieu M.
quelle
5
Das ist cool, aber es scheint mir, dass es nur für ganze Zahlen und die Varianten gilt. Was tun mit Floats / Doubles?
Brett
1
@ v.oddou: Ja und Nein, Speicherzuordnungsdateien sind genau die gleichen wie Netzwerk-Frames. wenn Sie akzeptieren nicht direkt zu lesen, alles, was zählt ist ihre endianness: wenn Little-Endian, die erste Formel verwenden, wenn es Big-Endian ist, verwenden Sie die zweite. Jeder Compiler, der sein Geld wert ist, optimiert nicht benötigte Transformationen, wenn die Endianness übereinstimmt.
Matthieu M.
2
@meowsqueak: Ja, ich würde erwarten, dass es funktioniert, da sich nur die Reihenfolge der Bytes ändert, nicht die Reihenfolge der Bits in jedem Byte.
Matthieu M.
3
In einem lockeren Zusammenhang ist der verlinkte Beitrag eine unangenehme Lektüre ... Der Typ scheint die Kürze zu schätzen, aber er zog es vor, eine lange Rede über all die schlechten Programmierer zu schreiben, die nicht so aufgeklärt sind wie er über Endianness, anstatt tatsächlich Erklären der Situation und WARUM seine Lösung immer funktioniert.
Ad N
1
Wenn Sie diese Methode verwenden, stellen Sie sicher, dass Sie Ihre Daten in (unsigned char *)
Joseph
51

Wenn Sie dies aus Gründen der Netzwerk- / Hostkompatibilität tun, sollten Sie Folgendes verwenden:

ntohl() //Network to Host byte order (Long)
htonl() //Host to Network byte order (Long)

ntohs() //Network to Host byte order (Short)
htons() //Host to Network byte order (Short)

Wenn Sie dies aus einem anderen Grund tun, funktioniert eine der hier vorgestellten byte_swap-Lösungen einwandfrei.

Eisig
quelle
2
Die Bestellung von Netzwerkbytes ist Big Endian, glaube ich. Diese Funktionen können in diesem Sinne verwendet werden, auch wenn Sie keinen Netzwerkcode verwenden. Es gibt jedoch keine Float-Versionen ntohf oder htonf
Matt
2
Matt H. das ist nur meistens richtig. Nicht alle Computersysteme haben eine Little-Endian-Bytereihenfolge. Wenn Sie beispielsweise an einem Motorolla 68k, einem PowerPC oder einer anderen Big-Endian-Architektur gearbeitet haben, werden diese Funktionen überhaupt keine Bytes austauschen, da sie bereits in der Reihenfolge der Netzwerkbytes vorliegen.
Frosty
2
Leider htonlund ntohlkann nicht zu Little Endian auf einer Big-Endian-Plattform gehen.
Brian Vandenberg
2
@ Celtschk, verstanden; Das OP möchte jedoch eine Möglichkeit, die Endianness zu wechseln, selbst in einer Big-Endian-Umgebung.
Brian Vandenberg
4
Um die unvermeidliche Frage abzuwenden: Es gibt eine Reihe von Gründen, LE für eine BE-Plattform zu benötigen. Eine Reihe von Dateiformaten (bmp, fli, pcx, qtm, rtf, tga, um nur einige zu nennen) verwenden kleine Endian-Werte ... oder zumindest eine Version des Formats auf einmal.
Brian Vandenberg
26

Ich habe ein paar Vorschläge aus diesem Beitrag genommen und sie zu diesem zusammengestellt:

#include <boost/type_traits.hpp>
#include <boost/static_assert.hpp>
#include <boost/detail/endian.hpp>
#include <stdexcept>

enum endianness
{
    little_endian,
    big_endian,
    network_endian = big_endian,

    #if defined(BOOST_LITTLE_ENDIAN)
        host_endian = little_endian
    #elif defined(BOOST_BIG_ENDIAN)
        host_endian = big_endian
    #else
        #error "unable to determine system endianness"
    #endif
};

namespace detail {

template<typename T, size_t sz>
struct swap_bytes
{
    inline T operator()(T val)
    {
        throw std::out_of_range("data size");
    }
};

template<typename T>
struct swap_bytes<T, 1>
{
    inline T operator()(T val)
    {
        return val;
    }
};

template<typename T>
struct swap_bytes<T, 2>
{
    inline T operator()(T val)
    {
        return ((((val) >> 8) & 0xff) | (((val) & 0xff) << 8));
    }
};

template<typename T>
struct swap_bytes<T, 4>
{
    inline T operator()(T val)
    {
        return ((((val) & 0xff000000) >> 24) |
                (((val) & 0x00ff0000) >>  8) |
                (((val) & 0x0000ff00) <<  8) |
                (((val) & 0x000000ff) << 24));
    }
};

template<>
struct swap_bytes<float, 4>
{
    inline float operator()(float val)
    {
        uint32_t mem =swap_bytes<uint32_t, sizeof(uint32_t)>()(*(uint32_t*)&val);
        return *(float*)&mem;
    }
};

template<typename T>
struct swap_bytes<T, 8>
{
    inline T operator()(T val)
    {
        return ((((val) & 0xff00000000000000ull) >> 56) |
                (((val) & 0x00ff000000000000ull) >> 40) |
                (((val) & 0x0000ff0000000000ull) >> 24) |
                (((val) & 0x000000ff00000000ull) >> 8 ) |
                (((val) & 0x00000000ff000000ull) << 8 ) |
                (((val) & 0x0000000000ff0000ull) << 24) |
                (((val) & 0x000000000000ff00ull) << 40) |
                (((val) & 0x00000000000000ffull) << 56));
    }
};

template<>
struct swap_bytes<double, 8>
{
    inline double operator()(double val)
    {
        uint64_t mem =swap_bytes<uint64_t, sizeof(uint64_t)>()(*(uint64_t*)&val);
        return *(double*)&mem;
    }
};

template<endianness from, endianness to, class T>
struct do_byte_swap
{
    inline T operator()(T value)
    {
        return swap_bytes<T, sizeof(T)>()(value);
    }
};
// specialisations when attempting to swap to the same endianess
template<class T> struct do_byte_swap<little_endian, little_endian, T> { inline T operator()(T value) { return value; } };
template<class T> struct do_byte_swap<big_endian,    big_endian,    T> { inline T operator()(T value) { return value; } };

} // namespace detail

template<endianness from, endianness to, class T>
inline T byte_swap(T value)
{
    // ensure the data is only 1, 2, 4 or 8 bytes
    BOOST_STATIC_ASSERT(sizeof(T) == 1 || sizeof(T) == 2 || sizeof(T) == 4 || sizeof(T) == 8);
    // ensure we're only swapping arithmetic types
    BOOST_STATIC_ASSERT(boost::is_arithmetic<T>::value);

    return detail::do_byte_swap<from, to, T>()(value);
}
Steve Lorimer
quelle
Sie müssen auch <cstdint> oder <stdint.h> einschließen, zum Beispiel für uint32_t
ady
17

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:

void swapByteOrder(unsigned short& us)
{
    us = (us >> 8) |
         (us << 8);
}

void swapByteOrder(unsigned int& ui)
{
    ui = (ui >> 24) |
         ((ui<<8) & 0x00FF0000) |
         ((ui>>8) & 0x0000FF00) |
         (ui << 24);
}

void swapByteOrder(unsigned long long& ull)
{
    ull = (ull >> 56) |
          ((ull<<40) & 0x00FF000000000000) |
          ((ull<<24) & 0x0000FF0000000000) |
          ((ull<<8) & 0x000000FF00000000) |
          ((ull>>8) & 0x00000000FF000000) |
          ((ull>>24) & 0x0000000000FF0000) |
          ((ull>>40) & 0x000000000000FF00) |
          (ull << 56);
}
Kevin
quelle
2
Die letzte hier veröffentlichte Funktion ist falsch und sollte wie folgt bearbeitet werden: void swapByteOrder (unsigned long long & ull) {ull = (ull >> 56) | ... (ull << 56); }
Eric Burnett
14
Ich denke nicht, dass es richtig ist, logisch und (&&) im Gegensatz zu bitweise und (&) zu verwenden. Gemäß der C ++ - Spezifikation werden beide Operanden implizit in bool konvertiert, was nicht das ist, was Sie wollen.
Trevor Robinson
16

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.

anon6439
quelle
Das ist eine großartige Verbindung. Es hat mein Interesse an x86 Assembler wiederbelebt.
PP.
1
Die Timing-Ergebnisse für BSWAP werden hier vorgestellt. gmplib.org/~tege/x86-timing.pdf ... und hier ... agner.org/optimize/instruction_tables.pdf
12

Wir haben dies mit Vorlagen gemacht. Sie könnten so etwas tun:

// Specialization for 2-byte types.
template<>
inline void endian_byte_swapper< 2 >(char* dest, char const* src)
{
    // Use bit manipulations instead of accessing individual bytes from memory, much faster.
    ushort* p_dest = reinterpret_cast< ushort* >(dest);
    ushort const* const p_src = reinterpret_cast< ushort const* >(src);
    *p_dest = (*p_src >> 8) | (*p_src << 8);
}

// Specialization for 4-byte types.
template<>
inline void endian_byte_swapper< 4 >(char* dest, char const* src)
{
    // Use bit manipulations instead of accessing individual bytes from memory, much faster.
    uint* p_dest = reinterpret_cast< uint* >(dest);
    uint const* const p_src = reinterpret_cast< uint const* >(src);
    *p_dest = (*p_src >> 24) | ((*p_src & 0x00ff0000) >> 8) | ((*p_src & 0x0000ff00) << 8) | (*p_src << 24);
}
Kennzeichen
quelle
8

Wenn Sie dies tun, um Daten zwischen verschiedenen Plattformen zu übertragen, sehen Sie sich die Funktionen ntoh und hton an.

Andrew
quelle
7

Genauso wie in C:

short big = 0xdead;
short little = (((big & 0xff)<<8) | ((big & 0xff00)>>8));

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.

Ben Straub
quelle
7

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:

unsigned int change_endian(unsigned int x)
{
    unsigned char *ptr = (unsigned char *)&x;
    return (ptr[0] << 24) | (ptr[1] << 16) | (ptr[2] << 8) | ptr[3];
}

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.

Terminus
quelle
6
Das ist lustig - der POSIX-Standard unter opengroup.org/onlinepubs/9699919799/toc.htm erwähnt keinen Header '<endian.h> `.
Jonathan Leffler
1
Sie können htonlund 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.)
Peter Cordes
5

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:

#define htonl(x) _byteswap_ulong(x)

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.

user2699548
quelle
4

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:

#include <iostream>

#include <endian.h>

template<size_t N> struct SizeT {};

#define BYTESWAPS(bits) \
template<class T> inline T htobe(T t, SizeT<bits / 8>) { return htobe ## bits(t); } \
template<class T> inline T htole(T t, SizeT<bits / 8>) { return htole ## bits(t); } \
template<class T> inline T betoh(T t, SizeT<bits / 8>) { return be ## bits ## toh(t); } \
template<class T> inline T letoh(T t, SizeT<bits / 8>) { return le ## bits ## toh(t); }

BYTESWAPS(16)
BYTESWAPS(32)
BYTESWAPS(64)

#undef BYTESWAPS

template<class T> inline T htobe(T t) { return htobe(t, SizeT<sizeof t>()); }
template<class T> inline T htole(T t) { return htole(t, SizeT<sizeof t>()); }
template<class T> inline T betoh(T t) { return betoh(t, SizeT<sizeof t>()); }
template<class T> inline T letoh(T t) { return letoh(t, SizeT<sizeof t>()); }

int main()
{
    std::cout << std::hex;
    std::cout << htobe(static_cast<unsigned short>(0xfeca)) << '\n';
    std::cout << htobe(0xafbeadde) << '\n';

    // Use ULL suffix to specify integer constant as unsigned long long 
    std::cout << htobe(0xfecaefbeafdeedfeULL) << '\n';
}

Ausgabe:

cafe
deadbeaf
feeddeafbeefcafe
Maxim Egorushkin
quelle
Änderung: #define BYTESWAPS (Bits) \ template <Klasse T> inline T htobe (T t, SizeT <bits / 8>) {return htobe ## bits (t); } \ template <Klasse T> inline T htole (T t, SizeT <bits / 8>) {return htole ## bits (t); } \ template <Klasse T> Inline T zwischen (T t, SizeT <bits / 8>) {return be ## bits ## toh (t); } \ template <Klasse T> inline T letoh (T t, Größe T <bits / 8>) {return le ## bits ## toh (t); }
ldav1s
Danke, habe vergessen, betoh () und letoh () zu testen.
Maxim Egorushkin
4

Ich mag dieses, nur für Stil :-)

long swap(long i) {
    char *c = (char *) &i;
    return * (long *) (char[]) {c[3], c[2], c[1], c[0] };
}
Friedemann
quelle
Ich bekomme eine Fehlermeldung char[] "Fehler: Unvollständiger Typ ist nicht zulässig"
Portland Runner
4

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?

template <typename T>
void SwapEnd(T& var)
{
    static_assert(std::is_pod<T>::value, "Type must be POD type for safety");
    std::array<char, sizeof(T)> varArray;
    std::memcpy(varArray.data(), &var, sizeof(T));
    for(int i = 0; i < static_cast<int>(sizeof(var)/2); i++)
        std::swap(varArray[sizeof(var) - 1 - i],varArray[i]);
    std::memcpy(&var, varArray.data(), sizeof(T));
}

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önnen uint8_t. Daher ist es richtig, dies in ein char-Array zu kopieren, auszutauschen und dann zurück zu kopieren (also nein reinterpret_cast). Beachten Sie, dass Compiler meistens intelligent genug sind, um das, was Sie getan haben, wieder in ein zu konvertieren, reinterpret_castwenn sie unabhängig von der Ausrichtung auf einzelne Bytes zugreifen können.

So verwenden Sie diese Funktion :

double x = 5;
SwapEnd(x);

und nun x ist anders in endianness.

Der Quantenphysiker
quelle
2
Dies wird überall funktionieren, aber die produzierte Assembly-OCDE ist oft nicht optimal
j_kubik
Sie verwenden new/ delete, um einen Puffer dafür zuzuweisen?!? sizeof(var)ist eine Konstante zur Kompilierungszeit, also können Sie dies tun char varSwapped[sizeof(var)]. Oder Sie könnten an Ort char *p = reinterpret_cast<char*>(&var)und Stelle tauschen.
Peter Cordes
@ Peter diese Antwort ist schnell und schmutzig gemacht, um einen Punkt zu beweisen. Ich werde Ihre Vorschläge umsetzen. Sie müssen jedoch kein Mega-SO AH sein und die 5-Zeilen-Lösung im Vergleich zu den dort aufgegebenen 50-Zeilen-Lösungen herabstimmen. Ich werde nicht mehr sagen.
Der Quantenphysiker
Diese Antwort enthält einige nützliche Punkte zum Umgang mit Konstruktoren und überladenen Operatoren bei Daten mit falschem Endian. Daher würde ich gerne meine Downvote entfernen, sobald der Code nicht schrecklich ist und etwas ist, das ein guter Compiler in einen bswap kompilieren könnte Anweisung. Außerdem würde ich vorschlagen, for(size_t i = 0 ; i < sizeof(var) ; i++)anstelle von a static_cast<long>. (Oder tatsächlich verwendet der In-Place-Tausch einen aufsteigenden und einen absteigenden, char*so dass er sowieso verschwindet).
Peter Cordes
Beispiel: Mark Ransoms Antwort mit std :: swap zum Umkehren an Ort und Stelle.
Peter Cordes
3

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:

// We define some constant for little, big and host endianess. Here I use 
// BOOST_LITTLE_ENDIAN/BOOST_BIG_ENDIAN to check the host indianess. If you
// don't want to use boost you will have to modify this part a bit.
enum EEndian
{
  LITTLE_ENDIAN_ORDER,
  BIG_ENDIAN_ORDER,
#if defined(BOOST_LITTLE_ENDIAN)
  HOST_ENDIAN_ORDER = LITTLE_ENDIAN_ORDER
#elif defined(BOOST_BIG_ENDIAN)
  HOST_ENDIAN_ORDER = BIG_ENDIAN_ORDER
#else
#error "Impossible de determiner l'indianness du systeme cible."
#endif
};

// this function swap the bytes of values given it's size as a template
// parameter (could sizeof be used?).
template <class T, unsigned int size>
inline T SwapBytes(T value)
{
  union
  {
     T value;
     char bytes[size];
  } in, out;

  in.value = value;

  for (unsigned int i = 0; i < size / 2; ++i)
  {
     out.bytes[i] = in.bytes[size - 1 - i];
     out.bytes[size - 1 - i] = in.bytes[i];
  }

  return out.value;
}

// Here is the function you will use. Again there is two compile-time assertion
// that use the boost librarie. You could probably comment them out, but if you
// do be cautious not to use this function for anything else than integers
// types. This function need to be calles like this :
//
//     int x = someValue;
//     int i = EndianSwapBytes<HOST_ENDIAN_ORDER, BIG_ENDIAN_ORDER>(x);
//
template<EEndian from, EEndian to, class T>
inline T EndianSwapBytes(T value)
{
  // A : La donnée à swapper à une taille de 2, 4 ou 8 octets
  BOOST_STATIC_ASSERT(sizeof(T) == 2 || sizeof(T) == 4 || sizeof(T) == 8);

  // A : La donnée à swapper est d'un type arithmetic
  BOOST_STATIC_ASSERT(boost::is_arithmetic<T>::value);

  // Si from et to sont du même type on ne swap pas.
  if (from == to)
     return value;

  return SwapBytes<T, sizeof(T)>(value);
}
Mathieu Pagé
quelle
3

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 ->

// can be used for short, unsigned short, word, unsigned word (2-byte types)
#define BYTESWAP16(n) (((n&0xFF00)>>8)|((n&0x00FF)<<8))

// can be used for int or unsigned int or float (4-byte types)
#define BYTESWAP32(n) ((BYTESWAP16((n&0xFFFF0000)>>16))|((BYTESWAP16(n&0x0000FFFF))<<16))

// can be used for unsigned long long or double (8-byte types)
#define BYTESWAP64(n) ((BYTESWAP32((n&0xFFFFFFFF00000000)>>32))|((BYTESWAP32(n&0x00000000FFFFFFFF))<<32))
Adam Freeman
quelle
2

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.

 template<typename T>
    void ByteSwap(T * p)
    {
        for (int i = 0;  i < sizeof(T)/2;  ++i)
            std::swap(((char *)p)[i], ((char *)p)[sizeof(T)-1-i]);
    }

Haftungsausschluss: Ich habe noch nicht versucht, dies zu kompilieren oder zu testen.

Mark Ransom
quelle
2

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:

x = ((x & 0x00000000ffffffff) << 32) ^ ((x >> 32) & 0x00000000ffffffff);
x = ((x & 0x0000ffff0000ffff) << 16) ^ ((x >> 16) & 0x0000ffff0000ffff);
x = ((x & 0x00ff00ff00ff00ff) <<  8) ^ ((x >>  8) & 0x00ff00ff00ff00ff);

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:

x = ( x                       << 32) ^  (x >> 32);

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:

uint64_t k = 0x00000000ffffffff; /* compiler should know a trick for this */
x = ((x & k) << 32) ^ ((x >> 32) & k);
k ^= k << 16;
x = ((x & k) << 16) ^ ((x >> 16) & k);
k ^= k << 8;
x = ((x & k) <<  8) ^ ((x >>  8) & k);

Wenn Sie möchten, können Sie dies als Schleife schreiben. Es wird nicht effizient sein, sondern nur zum Spaß:

int i = sizeof(x) * CHAR_BIT / 2;
uintmax_t k = (1 << i) - 1;
while (i >= 8)
{
    x = ((x & k) << i) ^ ((x >> i) & k);
    i >>= 1;
    k ^= k << i;
}

Der Vollständigkeit halber hier die vereinfachte 32-Bit-Version des ersten Formulars:

x = ( x               << 16) ^  (x >> 16);
x = ((x & 0x00ff00ff) <<  8) ^ ((x >>  8) & 0x00ff00ff);
sh1
quelle
2

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.

template<typename T> inline static T swapByteOrder(const T& val) {
    int totalBytes = sizeof(val);
    T swapped = (T) 0;
    for (int i = 0; i < totalBytes; ++i) {
        swapped |= (val >> (8*(totalBytes-i-1)) & 0xFF) << (8*i);
    }
    return swapped;
}
Joao
quelle
2

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.

Nick
quelle
2

Mit den folgenden Codes können Sie einfach zwischen BigEndian und LittleEndian wechseln

#define uint32_t unsigned 
#define uint16_t unsigned short

#define swap16(x) ((((uint16_t)(x) & 0x00ff)<<8)| \
(((uint16_t)(x) & 0xff00)>>8))

#define swap32(x) ((((uint32_t)(x) & 0x000000ff)<<24)| \
(((uint32_t)(x) & 0x0000ff00)<<8)| \
(((uint32_t)(x) & 0x00ff0000)>>8)| \
(((uint32_t)(x) & 0xff000000)>>24))
pz64_
quelle
1

Ich habe kürzlich ein Makro geschrieben, um dies in C zu tun, aber es ist auch in C ++ gültig:

#define REVERSE_BYTES(...) do for(size_t REVERSE_BYTES=0; REVERSE_BYTES<sizeof(__VA_ARGS__)>>1; ++REVERSE_BYTES)\
    ((unsigned char*)&(__VA_ARGS__))[REVERSE_BYTES] ^= ((unsigned char*)&(__VA_ARGS__))[sizeof(__VA_ARGS__)-1-REVERSE_BYTES],\
    ((unsigned char*)&(__VA_ARGS__))[sizeof(__VA_ARGS__)-1-REVERSE_BYTES] ^= ((unsigned char*)&(__VA_ARGS__))[REVERSE_BYTES],\
    ((unsigned char*)&(__VA_ARGS__))[REVERSE_BYTES] ^= ((unsigned char*)&(__VA_ARGS__))[sizeof(__VA_ARGS__)-1-REVERSE_BYTES];\
while(0)

Es akzeptiert jeden Typ und kehrt die Bytes im übergebenen Argument um. Beispielverwendungen:

int main(){
    unsigned long long x = 0xABCDEF0123456789;
    printf("Before: %llX\n",x);
    REVERSE_BYTES(x);
    printf("After : %llX\n",x);

    char c[7]="nametag";
    printf("Before: %c%c%c%c%c%c%c\n",c[0],c[1],c[2],c[3],c[4],c[5],c[6]);
    REVERSE_BYTES(c);
    printf("After : %c%c%c%c%c%c%c\n",c[0],c[1],c[2],c[3],c[4],c[5],c[6]);
}

Welche Drucke:

Before: ABCDEF0123456789
After : 8967452301EFCDAB
Before: nametag
After : gateman

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_BYTESals forZä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 forSchleife werden zwei Bytes referenziert und XOR ausgetauscht (daher ist kein temporärer Variablenname erforderlich):

((unsigned char*)&(__VA_ARGS__))[REVERSE_BYTES]
((unsigned char*)&(__VA_ARGS__))[sizeof(__VA_ARGS__)-1-REVERSE_BYTES]

__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 einen unsigned charZeiger 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.

Ryan Hilbert
quelle
fand dies irgendwo in einem Code. verwirrte mich zum Teufel. Danke für die Erklärung. Aber warum die Verwendung von __VA_ARGS__?
Asr9
0

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 ...

__declspec(naked) uint32_t EndianSwap(uint32 value)
{
    __asm
    {
        mov eax, dword ptr[esp + 4]
        bswap eax
        ret
    }
}

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.

unsigned long _byteswap_ulong(unsigned long value);

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 ...

mov ebx, eax
shr ebx, 16
xchg bl, bh
xchg al, ah
shl eax, 16
or eax, ebx

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.

Der Schweißer
quelle
1
_byteswap_ulongund _uint64(z. B. in der akzeptierten Antwort) beide kompilieren, um die bswapAnweisung 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?
ZachB
@stdcall Die Frage fragte nicht nach einer tragbaren Lösung oder erwähnte gar nichts über eine Plattform. Wie meine Antwort sagte, ist das Obige der schnellste Weg zum Endian-Tausch. Sicher, wenn Sie dies auf einer Nicht-X86-Plattform schreiben, wird dies nicht funktionieren, aber wie ich bereits erwähnt habe, sind Sie auf Compiler-Intrinsics beschränkt, wenn Ihr Compiler sie überhaupt unterstützt.
Der Schweißer
@ZachB In diesem speziellen Fall denke ich, dass das Weglassen des Prologs und des Epilogs eine anständige Ersparnis bringt, da Sie im Wesentlichen nur eine Anweisung ausführen. Der Prolog muss auf den Stapel geschoben werden, eine Subtraktion durchführen, den Basiszeiger setzen und dann am Ende ähnlich. Ich habe es nicht bewertet, aber das Obige hat eine 0-Abhängigkeitskette, die Sie einfach nicht bekommen werden, ohne dass es nackt ist. Vielleicht würde ein guter Compiler es inline, aber dann bist du in einem anderen Stadion.
Der Schweißer
2
Vielleicht. Beachten Sie jedoch, dass im allgemeinen Fall des Austauschs eines Arrays von Zahlen die in anderen Antworten beschriebenen Compiler-Eigenschaften SSE / AVX-Erweiterungen verwenden und PSHUFB ausgeben, das BSWAP übertrifft. Siehe wm.ite.pl/articles/reverse-array-of-bytes.html
ZachB
IMHO ist es schlecht, eine plattformspezifische Lösung zu veröffentlichen, wenn das OP nicht angegeben hat, dass nur eine Lösung für x86 benötigt wird. Und um die anderen Lösungen
herabzusetzen
0

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.

uint32_t sw_get_uint32_1234(pu32)
uint32_1234 *pu32;
{
  union {
    uint32_1234 u32_1234;
    uint32_t u32;
  } bou32;
  bou32.u32_1234[0] = (*pu32)[BO32_0];
  bou32.u32_1234[1] = (*pu32)[BO32_1];
  bou32.u32_1234[2] = (*pu32)[BO32_2];
  bou32.u32_1234[3] = (*pu32)[BO32_3];
  return(bou32.u32);
}

void sw_set_uint32_1234(pu32, u32)
uint32_1234 *pu32;
uint32_t u32;
{
  union {
    uint32_1234 u32_1234;
    uint32_t u32;
  } bou32;
  bou32.u32 = u32;
  (*pu32)[BO32_0] = bou32.u32_1234[0];
  (*pu32)[BO32_1] = bou32.u32_1234[1];
  (*pu32)[BO32_2] = bou32.u32_1234[2];
  (*pu32)[BO32_3] = bou32.u32_1234[3];
}

#if HAS_SW_INT64
int64 sw_get_int64_12345678(pi64)
int64_12345678 *pi64;
{
  union {
    int64_12345678 i64_12345678;
    int64 i64;
  } boi64;
  boi64.i64_12345678[0] = (*pi64)[BO64_0];
  boi64.i64_12345678[1] = (*pi64)[BO64_1];
  boi64.i64_12345678[2] = (*pi64)[BO64_2];
  boi64.i64_12345678[3] = (*pi64)[BO64_3];
  boi64.i64_12345678[4] = (*pi64)[BO64_4];
  boi64.i64_12345678[5] = (*pi64)[BO64_5];
  boi64.i64_12345678[6] = (*pi64)[BO64_6];
  boi64.i64_12345678[7] = (*pi64)[BO64_7];
  return(boi64.i64);
}
#endif

int32_t sw_get_int32_3412(pi32)
int32_3412 *pi32;
{
  union {
    int32_3412 i32_3412;
    int32_t i32;
  } boi32;
  boi32.i32_3412[2] = (*pi32)[BO32_0];
  boi32.i32_3412[3] = (*pi32)[BO32_1];
  boi32.i32_3412[0] = (*pi32)[BO32_2];
  boi32.i32_3412[1] = (*pi32)[BO32_3];
  return(boi32.i32);
}

void sw_set_int32_3412(pi32, i32)
int32_3412 *pi32;
int32_t i32;
{
  union {
    int32_3412 i32_3412;
    int32_t i32;
  } boi32;
  boi32.i32 = i32;
  (*pi32)[BO32_0] = boi32.i32_3412[2];
  (*pi32)[BO32_1] = boi32.i32_3412[3];
  (*pi32)[BO32_2] = boi32.i32_3412[0];
  (*pi32)[BO32_3] = boi32.i32_3412[1];
}

uint32_t sw_get_uint32_3412(pu32)
uint32_3412 *pu32;
{
  union {
    uint32_3412 u32_3412;
    uint32_t u32;
  } bou32;
  bou32.u32_3412[2] = (*pu32)[BO32_0];
  bou32.u32_3412[3] = (*pu32)[BO32_1];
  bou32.u32_3412[0] = (*pu32)[BO32_2];
  bou32.u32_3412[1] = (*pu32)[BO32_3];
  return(bou32.u32);
}

void sw_set_uint32_3412(pu32, u32)
uint32_3412 *pu32;
uint32_t u32;
{
  union {
    uint32_3412 u32_3412;
    uint32_t u32;
  } bou32;
  bou32.u32 = u32;
  (*pu32)[BO32_0] = bou32.u32_3412[2];
  (*pu32)[BO32_1] = bou32.u32_3412[3];
  (*pu32)[BO32_2] = bou32.u32_3412[0];
  (*pu32)[BO32_3] = bou32.u32_3412[1];
}

float sw_get_float_1234(pf)
float_1234 *pf;
{
  union {
    float_1234 f_1234;
    float f;
  } bof;
  bof.f_1234[0] = (*pf)[BO32_0];
  bof.f_1234[1] = (*pf)[BO32_1];
  bof.f_1234[2] = (*pf)[BO32_2];
  bof.f_1234[3] = (*pf)[BO32_3];
  return(bof.f);
}

void sw_set_float_1234(pf, f)
float_1234 *pf;
float f;
{
  union {
    float_1234 f_1234;
    float f;
  } bof;
  bof.f = (float)f;
  (*pf)[BO32_0] = bof.f_1234[0];
  (*pf)[BO32_1] = bof.f_1234[1];
  (*pf)[BO32_2] = bof.f_1234[2];
  (*pf)[BO32_3] = bof.f_1234[3];
}

double sw_get_double_12345678(pd)
double_12345678 *pd;
{
  union {
    double_12345678 d_12345678;
    double d;
  } bod;
  bod.d_12345678[0] = (*pd)[BO64_0];
  bod.d_12345678[1] = (*pd)[BO64_1];
  bod.d_12345678[2] = (*pd)[BO64_2];
  bod.d_12345678[3] = (*pd)[BO64_3];
  bod.d_12345678[4] = (*pd)[BO64_4];
  bod.d_12345678[5] = (*pd)[BO64_5];
  bod.d_12345678[6] = (*pd)[BO64_6];
  bod.d_12345678[7] = (*pd)[BO64_7];
  return(bod.d);
}

void sw_set_double_12345678(pd, d)
double_12345678 *pd;
double d;
{
  union {
    double_12345678 d_12345678;
    double d;
  } bod;
  bod.d = d;
  (*pd)[BO64_0] = bod.d_12345678[0];
  (*pd)[BO64_1] = bod.d_12345678[1];
  (*pd)[BO64_2] = bod.d_12345678[2];
  (*pd)[BO64_3] = bod.d_12345678[3];
  (*pd)[BO64_4] = bod.d_12345678[4];
  (*pd)[BO64_5] = bod.d_12345678[5];
  (*pd)[BO64_6] = bod.d_12345678[6];
  (*pd)[BO64_7] = bod.d_12345678[7];
}

Diese Typedefs haben den Vorteil, dass sie Compilerfehler auslösen, wenn sie nicht mit Accessoren verwendet werden, wodurch vergessene Accessor-Fehler verringert werden.

typedef char int8_1[1], uint8_1[1];

typedef char int16_12[2], uint16_12[2]; /* little endian */
typedef char int16_21[2], uint16_21[2]; /* big endian */

typedef char int24_321[3], uint24_321[3]; /* Alpha Micro, PDP-11 */

typedef char int32_1234[4], uint32_1234[4]; /* little endian */
typedef char int32_3412[4], uint32_3412[4]; /* Alpha Micro, PDP-11 */
typedef char int32_4321[4], uint32_4321[4]; /* big endian */

typedef char int64_12345678[8], uint64_12345678[8]; /* little endian */
typedef char int64_34128756[8], uint64_34128756[8]; /* Alpha Micro, PDP-11 */
typedef char int64_87654321[8], uint64_87654321[8]; /* big endian */

typedef char float_1234[4]; /* little endian */
typedef char float_3412[4]; /* Alpha Micro, PDP-11 */
typedef char float_4321[4]; /* big endian */

typedef char double_12345678[8]; /* little endian */
typedef char double_78563412[8]; /* Alpha Micro? */
typedef char double_87654321[8]; /* big endian */
BSalita
quelle
2
Bei dieser Frage macht das C ++ - Tag einen Unterschied. Aufgrund von C ++ und der Union gibt es viele undefinierte Verhaltensweisen.
JWW
0

So lesen Sie ein im IEEE 754 64-Bit-Format gespeichertes Double, auch wenn Ihr Host-Computer ein anderes System verwendet.

/*
* read a double from a stream in ieee754 format regardless of host
*  encoding.
*  fp - the stream
*  bigendian - set to if big bytes first, clear for little bytes
*              first
*
*/
double freadieee754(FILE *fp, int bigendian)
{
    unsigned char buff[8];
    int i;
    double fnorm = 0.0;
    unsigned char temp;
    int sign;
    int exponent;
    double bitval;
    int maski, mask;
    int expbits = 11;
    int significandbits = 52;
    int shift;
    double answer;

    /* read the data */
    for (i = 0; i < 8; i++)
        buff[i] = fgetc(fp);
    /* just reverse if not big-endian*/
    if (!bigendian)
    {
        for (i = 0; i < 4; i++)
        {
            temp = buff[i];
            buff[i] = buff[8 - i - 1];
            buff[8 - i - 1] = temp;
        }
    }
    sign = buff[0] & 0x80 ? -1 : 1;
    /* exponet in raw format*/
    exponent = ((buff[0] & 0x7F) << 4) | ((buff[1] & 0xF0) >> 4);

    /* read inthe mantissa. Top bit is 0.5, the successive bits half*/
    bitval = 0.5;
    maski = 1;
    mask = 0x08;
    for (i = 0; i < significandbits; i++)
    {
        if (buff[maski] & mask)
            fnorm += bitval;

        bitval /= 2.0;
        mask >>= 1;
        if (mask == 0)
        {
            mask = 0x80;
            maski++;
        }
    }
    /* handle zero specially */
    if (exponent == 0 && fnorm == 0)
        return 0.0;

    shift = exponent - ((1 << (expbits - 1)) - 1); /* exponent = shift + bias */
    /* nans have exp 1024 and non-zero mantissa */
    if (shift == 1024 && fnorm != 0)
        return sqrt(-1.0);
    /*infinity*/
    if (shift == 1024 && fnorm == 0)
    {

#ifdef INFINITY
        return sign == 1 ? INFINITY : -INFINITY;
#endif
        return  (sign * 1.0) / 0.0;
    }
    if (shift > -1023)
    {
        answer = ldexp(fnorm + 1.0, shift);
        return answer * sign;
    }
    else
    {
        /* denormalised numbers */
        if (fnorm == 0.0)
            return 0.0;
        shift = -1022;
        while (fnorm < 1.0)
        {
            fnorm *= 2;
            shift--;
        }
        answer = ldexp(fnorm, shift);
        return answer * sign;
    }
}

Für den Rest der Funktionssuite, einschließlich der Schreib- und Ganzzahlroutinen, siehe mein Github-Projekt

https://github.com/MalcolmMcLean/ieee754

Malcolm McLean
quelle
0

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:

template<typename T>void swap(T &t){
    for(uint8_t pivot = 0; pivot < sizeof(t)/2; pivot ++){
        *((uint8_t *)&t + pivot) ^= *((uint8_t *)&t+sizeof(t)-1- pivot);
        *((uint8_t *)&t+sizeof(t)-1- pivot) ^= *((uint8_t *)&t + pivot);
        *((uint8_t *)&t + pivot) ^= *((uint8_t *)&t+sizeof(t)-1- pivot);
    }
}
Quinn Carver
quelle
0

Es scheint, als wäre der sichere Weg, für jedes Wort htons zu verwenden. Also, wenn Sie haben ...

std::vector<uint16_t> storage(n);  // where n is the number to be converted

// the following would do the trick
std::transform(word_storage.cbegin(), word_storage.cend()
  , word_storage.begin(), [](const uint16_t input)->uint16_t {
  return htons(input); });

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 ...

#if (__DARWIN_BYTE_ORDER != __DARWIN_BIG_ENDIAN)
std::transform(word_storage.cbegin(), word_storage.cend()
  , word_storage.begin(), [](const uint16_t input)->uint16_t {
  return htons(input); });
#endif
Cycolline
quelle
0

Wenn Sie C ++ 17 haben, fügen Sie diesen Header hinzu

#include <algorithm>

Verwenden Sie diese Vorlagenfunktion, um die Bytes auszutauschen:

template <typename T>
void swapEndian(T& buffer)
{
    static_assert(std::is_pod<T>::value, "swapEndian support POD type only");
    char* startIndex = static_cast<char*>((void*)buffer.data());
    char* endIndex = startIndex + sizeof(buffer);
    std::reverse(startIndex, endIndex);
}

nenne es wie:

swapEndian (stlContainer);
Thinkal VB
quelle
-4

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.

Roter Baron
quelle