Jak przekonwertować między wartościami big-endian i little-endian w C ++?


196

Jak przekonwertować między wartościami big-endian i little-endian w C ++?

EDYCJA: Dla jasności muszę tłumaczyć dane binarne (zmiennoprzecinkowe podwójnej precyzji oraz 32-bitowe i 64-bitowe liczby całkowite) z jednej architektury procesora na drugą. Nie wymaga to pracy w sieci, więc ntoh () i podobne funkcje tutaj nie będą działać.

EDYCJA 2: Odpowiedź, którą zaakceptowałem, dotyczy bezpośrednio kompilatorów, na które celuję (dlatego ją wybrałem). Istnieją jednak inne bardzo dobre, bardziej przenośne odpowiedzi.


21
ntoh hton będzie działał dobrze, nawet jeśli nie ma to nic wspólnego z siecią.
Ben Collins,

2
Najlepszym sposobem radzenia sobie z endianingiem jest upewnienie się, że kod działa zarówno na małych, jak i dużych komputerach hosta. Jeśli to działa, prawdopodobnie zrobiłeś to dobrze. Zakładanie, że jesteś na x86 / be, jest niebezpieczne jako praktyka.
jakobengblom2

10
hton ntoh nie będzie działać, jeśli maszyna jest big-endian, ponieważ pytający wyraźnie chce wykonać konwersję.
fabspro

6
@ jakobengblom2 jest jedyną osobą, która o tym wspomina. Prawie wszystkie przykłady na tej stronie używają pojęć takich jak „zamień” bajty zamiast robić to niezależnie od leżącej u ich podstaw endianizmu. Jeśli masz do czynienia z zewnętrznymi formatami plików (które mają dobrze zdefiniowaną endianowość), najbardziej przenośnym rozwiązaniem jest traktowanie danych zewnętrznych jako strumienia bajtów i konwersja strumienia bajtów na rodzime liczby całkowite. Kulę się za każdym razem, gdy widzę short swap(short x)kod, ponieważ pęknie, jeśli przejdziesz na platformę z innym endianizmem. Matthieu M ma jedyną właściwą odpowiedź poniżej.
Mark Lakata,

3
Myślisz o problemie całkowicie źle. Zadanie nie polega na tym, „jak przekonwertować między wartościami big-endian i little-endian”. Zadanie polega na tym: „jak przekonwertować wartości zmiennoprzecinkowe i liczby całkowite w określonym formacie na format macierzysty mojej platformy”. Jeśli zrobisz to dobrze, natywny format może być big endian, little endian, mixed endian lub ternary dla wszystkich twoich spraw związanych z kodem.
David Schwartz,

Odpowiedzi:


166

Jeśli używasz Visual C ++ wykonaj następujące czynności: Dołącz intrin.h i wywołaj następujące funkcje:

Dla liczb 16-bitowych:

unsigned short _byteswap_ushort(unsigned short value);

Dla liczb 32-bitowych:

unsigned long _byteswap_ulong(unsigned long value);

Dla liczb 64-bitowych:

unsigned __int64 _byteswap_uint64(unsigned __int64 value);

Liczby 8-bitowe (znaki) nie muszą być konwertowane.

Są one również zdefiniowane tylko dla niepodpisanych wartości, działają również dla liczb całkowitych ze znakiem.

W przypadku liczb zmiennoprzecinkowych i podwójnych jest to trudniejsze niż w przypadku zwykłych liczb całkowitych, ponieważ mogą one być lub nie w kolejności bajtów na komputerach głównych. Możesz uzyskać spławiki little-endian na maszynach big-endian i odwrotnie.

Inne kompilatory mają również podobne cechy wewnętrzne.

Na przykład w GCC możesz bezpośrednio wywoływać niektóre wbudowane funkcje, jak tu udokumentowano :

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

(nie trzeba niczego dołączać). Afaik bits.h deklaruje tę samą funkcję również w sposób niecentralny na gcc.

16-bitowa zamiana to tylko odrobina obrotu.

Wywołanie funkcji wewnętrznych zamiast tworzenia własnych daje najlepszą wydajność i gęstość kodu btw ..


11
Z GCC mogę użyć: #include <byteswap.h> int32_t bswap_32 (int32_t x) int64_t bswap_64 (int64_t x)
jmanning2k

5
__builtin_bswapXjest dostępna tylko od GCC-4.3 r.
Matt Joiner

20
Warto również zauważyć, że te intrinsics / zawsze / zamiana bajtów, a nie jak są one htonl, htonsitp Trzeba wiedzieć z kontekstu sytuacji, gdy rzeczywiście zamienić bajtów.
Brian Vandenberg,

8
@Jason, ponieważ 8-bitowe liczby są takie same w dużych i małych endianach. :-)
Nils Pipenbrinck

2
@BrianVandenberg Right; używanie htonli ntohlbez martwienia się o kontekst zadziałałoby podczas pisania przenośnego kodu, ponieważ platforma definiująca te funkcje zamieniłaby go, gdyby był mały / mid-endian, a na big-endian byłby niemożliwy. Jednak podczas dekodowania standardowego typu pliku, który jest zdefiniowany jako little-endian (powiedzmy BMP), nadal trzeba znać kontekst i nie można po prostu polegać na htonli ntohl.
legends2k

86

Po prostu:

#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;
}

Zastosowanie: swap_endian<uint32_t>(42).


3
Zyskaj głos. Właśnie użyłem ucharów i przypisałem 4 do 1, 3 do 2, 2 do 3 i 1 do 4, ale jest to bardziej elastyczne, jeśli masz różne rozmiary. 6 zegarów w Pentium IIRC 1. generacji. BSWAP ma 1 zegar, ale jest specyficzny dla platformy.

2
@RocketRoy: Tak, a jeśli prędkość okazuje się być problemem, bardzo łatwo jest pisać przeciążenia intrisami specyficznymi dla platformy i typu.
Alexandre C.

3
@MihaiTodor: To użycie związków do rzutowania czcionek za pomocą tablicy znaków jest wyraźnie dozwolone przez standard. Zobacz np. to pytanie .
Alexandre C.

4
@AlexandreC. Nie w standardzie C ++ - tylko w C. W C ++ (którym jest ten kod) zachowanie tego kodu jest niezdefiniowane.
Rapptz

4
@Rapptz: 3.10 wydaje się jasne: „Jeśli program próbuje uzyskać dostęp do przechowywanej wartości obiektu przez wartość glvalue innego niż jeden z następujących typów, zachowanie jest niezdefiniowane: [...] char lub niepodpisany typ char. ”. Może czegoś mi brakuje, ale było dla mnie całkiem jasne, że dostęp do dowolnego typu za pomocą wskaźników char jest wyraźnie dozwolony.
Alexandre C.,

75

From The Byte Order Fallacy autorstwa Rob Pike:

Powiedzmy, że twój strumień danych ma 32-bitową liczbę całkowitą zakodowaną według Endian. Oto jak go wyodrębnić (przy założeniu bajtów niepodpisanych):

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

Jeśli jest to big-endian, oto jak go wyodrębnić:

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

TL; DR: nie martw się o natywną kolejność platformy, liczy się tylko kolejność bajtów strumienia, z którego czytasz, i lepiej mieć nadzieję, że jest dobrze zdefiniowana.

Uwaga: w komentarzu zaznaczono brak konwersji typu jawnego, ważne było, aby databyła tablicą unsigned charlub uint8_t. Użycie signed charlub char(jeśli podpisane) spowoduje data[x]awans na liczbę całkowitą i data[x] << 24potencjalnie przesunięcie 1 do bitu znaku, który jest UB.


5
To jest fajne, ale wydaje mi się, że dotyczy tylko liczb całkowitych i wariantów. Co zrobić z float / double?
Brett,

1
@ v.oddou: tak i nie, pliki mapowane w pamięci są dokładnie takie same jak ramki sieciowe; jeśli zgadzasz się nie czytać ich bezpośrednio, liczy się tylko ich endianizm: jeśli little-endian, użyj pierwszej formuły, jeśli jest to big-endian, użyj drugiej. Każdy kompilator wart swojej soli zoptymalizuje niepotrzebne transformacje, jeśli endianness się zgadza.
Matthieu M.

2
@meowsqueak: Tak, oczekiwałbym, że to działa, ponieważ zmienia się tylko kolejność bajtów, a nie kolejność bitów w każdym bajcie.
Matthieu M.

3
W luźno powiązanej notatce link jest nieprzyjemną lekturą ... Facet wydaje się cenić zwięzłość, ale wolał napisać długie zdanie na temat tych wszystkich złych programistów, którzy nie są tak oświeceni jak on w odniesieniu do endianizmu, zamiast w rzeczywistości wyjaśnienie sytuacji i DLACZEGO jego rozwiązanie zawsze działa.
Ogłoszenie

1
Jeśli używasz tej metody, upewnij się, że przesyłasz swoje dane do (unsigned char *)
Joseph

51

Jeśli robisz to dla celów kompatybilności sieci / hosta, powinieneś użyć:

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)

Jeśli robisz to z innego powodu, jedno z przedstawionych tutaj rozwiązań byte_swap będzie działać dobrze.


2
sieć bajtowanie jest wielkim endianem. Tych funkcji można używać w tym celu, nawet jeśli nie używasz kodu sieciowego. Jednak nie ma wersji pływających ntohf lub htonf
Matt

2
Matt H. to tylko w większości poprawne. Nie wszystkie systemy komputerowe mają porządek bajtów endian. Jeśli pracujesz, powiedzmy motorolla 68k, PowerPC lub inną architekturę big-endian, funkcje te w ogóle nie będą zamieniały bajtów, ponieważ są już w 'kolejności bajtów sieciowych.
Frosty

2
Niestety htonli ntohlnie można przejść do małego endiana na platformie big-endian.
Brian Vandenberg,

2
@celtschk, zrozumiany; jednak OP chce sposobu na zmianę endianizmu, nawet w środowisku big-endian.
Brian Vandenberg

4
Aby odpowiedzieć na nieuniknione pytanie: istnieje wiele powodów, dla których potrzebny jest LE dla platformy BE; wiele formatów plików (bmp, fli, pcx, qtm, rtf, tga, aby wymienić tylko kilka) używa małych wartości endian ... a przynajmniej niektóre wersje tego formatu zrobiły to jednocześnie.
Brian Vandenberg

26

Wziąłem kilka sugestii z tego postu i złożyłem je razem, aby utworzyć:

#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);
}

musisz również dołączyć <cstdint> lub <stdint.h>, na przykład dla uint32_t
ady

17

Procedura przejścia z big-endian do little-endian jest taka sama, jak przejście z little-endian do big-endian.

Oto przykładowy kod:

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);
}

2
Ostatnia opublikowana tutaj funkcja jest niepoprawna i powinna być edytowana do: void swapByteOrder (niepodpisany długi długi i ull) {ull = (ull >> 56) | ... (ull << 56); }
Eric Burnett,

14
Nie sądzę, aby stosowanie logicznych i (&&) w przeciwieństwie do bitowego-i (&) było poprawne. Zgodnie ze specyfikacją C ++ oba operandy są domyślnie konwertowane na bool, co nie jest tym, czego chcesz.
Trevor Robinson

16

Istnieje instrukcja montażu o nazwie BSWAP, która zrobi za ciebie, bardzo szybko . Możesz przeczytać o tym tutaj .

Visual Studio, a ściślej biblioteka uruchomieniowa Visual C ++, ma w tym celu swoistą platformę, tzw _byteswap_ushort(), _byteswap_ulong(), and _byteswap_int64(). Podobne powinny istnieć dla innych platform, ale nie wiem, jak by się nazywały.


To świetny link. Ożywiło moje zainteresowanie asemblerem x86.
PP.

1
Wyniki czasowe dla BSWAP są przedstawione tutaj. gmplib.org/~tege/x86-timing.pdf ... a tutaj ... agner.org/optimize/instruction_tables.pdf

12

Zrobiliśmy to za pomocą szablonów. Możesz zrobić coś takiego:

// 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);
}

8

Jeśli robisz to, aby przesyłać dane między różnymi platformami, spójrz na funkcje ntoh i hton.


7

Tak samo jak w C:

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

Możesz także zadeklarować wektor znaków bez znaku, zapisz w nim wartość wejściową, odwróć bajty na inny wektor i zapisz bajty na zewnątrz, ale zajmie to rząd wielkości dłuższy niż kręcenie bitów, szczególnie w przypadku wartości 64-bitowych.


7

W większości systemów POSIX (ponieważ nie ma go w standardzie POSIX) istnieje endian.h, którego można użyć do określenia, jakiego kodowania używa twój system. Stamtąd jest coś takiego:

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];
}

Spowoduje to zamianę kolejności (z dużego endianu na mały endian):

Jeśli masz liczbę 0xDEADBEEF (w małym systemie endian przechowywanym jako 0xEFBEADDE), ptr [0] będzie 0xEF, ptr [1] to 0xBE itp.

Ale jeśli chcesz go używać do pracy w sieci, hton, htonl i htonll (i ich odwrócone ntohs, ntohl i ntohll) będą pomocne w konwersji z hosta na porządek sieciowy.


6
To zabawne - standard POSIX na opengroup.org/onlinepubs/9699919799/toc.htm nie wspomina o nagłówku <endian.h>.
Jonathan Leffler,

1
Możesz używać htonli znajomych niezależnie od tego, czy skrzynka ma coś wspólnego z siecią. Kolejność bajtów w sieci jest wielka, więc potraktuj te funkcje jako host_to_be i be_to_host. (Nie pomaga, jeśli potrzebujesz host_to_le.)
Peter Cordes

5

Zauważ, że przynajmniej dla Windows htonl () jest znacznie wolniejszy niż ich wewnętrzny odpowiednik _byteswap_ulong (). Pierwsze z nich to wywołanie biblioteki DLL do pliku ws2_32.dll, drugie to jedna instrukcja montażu BSWAP. Dlatego, jeśli piszesz kod zależny od platformy, wolisz używać wewnętrznych funkcji dla szybkości:

#define htonl(x) _byteswap_ulong(x)

Może to być szczególnie ważne w przypadku przetwarzania obrazu .PNG, gdzie wszystkie liczby całkowite są zapisywane w Big Endian z wyjaśnieniem „Można użyć htonl () ...” {aby spowolnić typowe programy Windows, jeśli nie jesteś przygotowany}.


4

Większość platform ma systemowy plik nagłówkowy, który zapewnia wydajne funkcje wymiany bajtów. W Linuksie jest <endian.h>. Możesz to dobrze owinąć w C ++:

#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';
}

Wynik:

cafe
deadbeaf
feeddeafbeefcafe

Zmień: # zdefiniuj BYTESWAPS (bity) \ szablon <klasa T> wbudowany T htobe (T t, SizeT <bity / 8>) {return htobe ## bits (t); } \ template <klasa T> inline T htole (T t, SizeT <bity / 8>) {return htole ## bits (t); } \ template <klasa T> inline T betoh (T t, SizeT <bity / 8>) {return be ## bits ## toh (t); } \ template <klasa T> inline T letoh (T t, SizeT <bity / 8>) {return le ## bits ## toh (t); }
ldav1s,

Dzięki, zapomniałem przetestować betoh () i letoh ().
Maxim Egorushkin

4

podoba mi się ten, tylko dla stylu :-)

long swap(long i) {
    char *c = (char *) &i;
    return * (long *) (char[]) {c[3], c[2], c[1], c[0] };
}

char[]Pojawia się komunikat o błędzie „Błąd: niedokończony typ jest niedozwolony”
Portland Runner,

4

Poważnie ... Nie rozumiem, dlaczego wszystkie rozwiązania są tak skomplikowane ! Co powiesz na najprostszą, najbardziej ogólną funkcję szablonu, która zamienia dowolny typ dowolnego rozmiaru w dowolnych okolicznościach w dowolnym systemie operacyjnym ????

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));
}

To magiczna moc C i C ++ razem! Po prostu zamień oryginalny zmienny znak po znaku.

Punkt 1 : Brak operatorów: Pamiętaj, że nie użyłem prostego operatora przypisania "=", ponieważ niektóre obiekty zostaną pomieszane, gdy endianness zostanie odwrócony, a konstruktor kopiowania (lub operator przypisania) nie będzie działał. Dlatego bardziej niezawodne jest kopiowanie ich char po char.

Punkt 2 : Należy pamiętać o problemach z wyrównaniem: zauważ, że kopiujemy do iz tablicy, co jest słuszne, ponieważ kompilator C ++ nie gwarantuje, że możemy uzyskać dostęp do niewyrównanej pamięci (ta odpowiedź została zaktualizowana z oryginalnej formularz do tego). Na przykład, jeśli przydzielisz uint64_t, kompilator nie może zagwarantować, że możesz uzyskać dostęp do 3-go bajtu jako uint8_t. Dlatego właściwą rzeczą jest skopiowanie tego do tablicy char, zamiana, a następnie skopiowanie z powrotem (więc nie reinterpret_cast). Zauważ, że kompilatory są w większości wystarczająco inteligentne, aby przekonwertować to, co zrobiłeś z powrotem na, reinterpret_castjeśli są w stanie uzyskać dostęp do poszczególnych bajtów niezależnie od wyrównania.

Aby użyć tej funkcji :

double x = 5;
SwapEnd(x);

a teraz xróżni się endianizmem.


2
Działa to wszędzie, ale wyprodukowany zestaw jest często nieoptymalny: patrz moje pytanie stackoverflow.com/questions/36657895/...
j_kubik 16.04.16

Używasz new/ deletedo przydzielenia bufora?!? sizeof(var)jest stałą czasową kompilacji, więc możesz to zrobić char varSwapped[sizeof(var)]. Lub możesz to zrobić char *p = reinterpret_cast<char*>(&var)i zamienić w miejscu.
Peter Cordes

@Peter ta odpowiedź jest szybka i brudna. Realizuję twoje sugestie. Jednak nie musisz być mega SO AH i głosować w dół 5-liniowym rozwiązaniem w porównaniu do rozwiązań 50-liniowych, które tam są. Nie powiem więcej.
Fizyk kwantowy

Ta odpowiedź zawiera kilka użytecznych uwag na temat ostrożności z konstruktorami i przeciążonymi operatorami niewłaściwych danych endianów, więc chętnie usunę moją opinię, gdy kod nie będzie okropny, i jest to coś, co dobry kompilator mógłby skompilować w bswap instrukcja. Ponadto sugerowałbym użycie for(size_t i = 0 ; i < sizeof(var) ; i++)zamiast static_cast<long>. (Lub właściwie zamiana w miejscu użyje rosnącej i malejącej, char*więc i tak zniknie).
Peter Cordes

np. patrz odpowiedź Marka Ransoma przy użyciu std :: swap, aby cofnąć w miejscu.
Peter Cordes

3

Mam ten kod, który pozwala mi na konwersję z HOST_ENDIAN_ORDER (cokolwiek to jest) na LITTLE_ENDIAN_ORDER lub BIG_ENDIAN_ORDER. Korzystam z szablonu, więc jeśli spróbuję przekonwertować z HOST_ENDIAN_ORDER na LITTLE_ENDIAN_ORDER i będą one takie same dla komputera, dla którego skompiluję, kod nie zostanie wygenerowany.

Oto kod z kilkoma komentarzami:

// 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);
}

3

Jeśli 32-bitowa liczba całkowita bez znaku big-endian wygląda jak 0xAABBCCDD, co jest równe 2864434397, to ta sama 32-bitowa liczba całkowita bez znaku wygląda jak 0xDDCCBBAA na procesorze little-endian, który jest również równy 2864434397.

Jeśli 16-bitowy skrót bez znaku big-endian wygląda jak 0xAABB, co jest równe 43707, to ten sam 16-bitowy skrót bez znaku wygląda jak 0xBBAA na procesorze little-endian, który jest również równy 43707.

Oto kilka przydatnych #define funkcji do zamiany bajtów z little-endian na big-endian i odwrotnie ->

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

2

Oto uogólniona wersja, którą wymyśliłem z góry głowy, do zamiany wartości w miejscu. Inne sugestie byłyby lepsze, jeśli wydajność stanowi problem.

 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]);
    }

Oświadczenie: Nie próbowałem tego kompilować ani testować.


2

Jeśli weźmiesz wspólny wzorzec do odwracania kolejności bitów w słowie i usuniesz część odwracającą bity w każdym bajcie, wówczas pozostanie Ci coś, co odwraca tylko bajty w słowie. Dla 64-bitów:

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

Kompilator powinien wyczyścić zbędne operacje maskowania bitów (zostawiłem je, aby podświetlić wzorzec), ale jeśli nie, możesz przepisać pierwszą linię w ten sposób:

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

Zwykle powinno to uprościć jedną instrukcję rotacji na większości architektur (ignorując, że cała operacja jest prawdopodobnie jedną instrukcją).

W procesorze RISC duże, skomplikowane stałe mogą powodować trudności kompilatora. Możesz jednak w prosty sposób obliczyć każdą ze stałych z poprzedniej. Tak jak:

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

Jeśli chcesz, możesz napisać to w pętli. To nie będzie wydajne, ale dla zabawy:

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

A dla kompletności, oto uproszczona 32-bitowa wersja pierwszego formularza:

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

2

Pomyślałem, że dodałem tutaj własne rozwiązanie, ponieważ nigdzie go nie widziałem. Jest to mała i przenośna funkcja szablonowana w języku C ++ i przenośna, która wykorzystuje tylko operacje bitowe.

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

2

Jestem naprawdę zaskoczony, że nikt nie wspomniał o funkcjach htobeXX i betohXX. Są zdefiniowane w endian.h i są bardzo podobne do funkcji sieciowych htonXX.


2

Korzystając z poniższych kodów, możesz łatwo przełączać się między BigEndian i LittleEndian

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

1

Niedawno napisałem makro, aby to zrobić w C, ale jest równie poprawne w C ++:

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

Akceptuje dowolny typ i odwraca bajty w przekazanym argumencie. Przykładowe zastosowania:

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]);
}

Które wydruki:

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

Powyższe jest doskonale możliwe do kopiowania / wklejania, ale tutaj dużo się dzieje, więc podzielę się tym, jak to działa kawałek po kawałku:

Pierwszą godną uwagi rzeczą jest to, że całe makro jest zamknięte w do while(0)bloku. To jest powszechny idiom umożliwiający normalne użycie średnika po makrze.

Następna w kolejce jest użycie zmiennej o nazwie REVERSE_BYTESjakfor licznik pętli jest. Nazwa samego makra jest używana jako nazwa zmiennej, aby upewnić się, że nie koliduje on z innymi symbolami, które mogą być w zasięgu wszędzie tam, gdzie makro jest używane. Ponieważ nazwa jest używana w ramach rozwinięcia makra, nie zostanie ponownie rozwinięta, gdy zostanie użyta tutaj jako nazwa zmiennej.

W forpętli są dwa bajty, do których się odwołujemy, i zamiana XOR (więc nazwa zmiennej tymczasowej nie jest wymagana):

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

__VA_ARGS__reprezentuje to, co zostało dane makrze, i służy do zwiększenia elastyczności tego, co może zostać przekazane (choć niewiele). Adres tego argumentu jest następnie pobierany i przesyłany do unsigned charwskaźnika, aby umożliwić zamianę jego bajtów poprzez []indeksowanie tablicy .

Ostatnim szczególnym punktem jest brak {}nawiasów klamrowych. Nie są one konieczne, ponieważ wszystkie kroki w każdej zamianie są połączone z operatorem przecinkowym , co czyni je jedną instrukcją.

Na koniec warto zauważyć, że nie jest to idealne podejście, jeśli priorytetem jest prędkość. Jeśli jest to ważny czynnik, niektóre makra specyficzne dla typu lub dyrektywy specyficzne dla platformy, o których mowa w innych odpowiedziach, są prawdopodobnie lepszą opcją. Takie podejście jest jednak przenośne dla wszystkich typów, wszystkich głównych platform oraz języków C i C ++.


znalazłem to gdzieś w jakimś kodzie. zdezorientował mnie cholernie. Dziękuję za wyjaśnienie. Jednak dlaczego korzystanie z __VA_ARGS__?
asr9

0

Wow, nie mogłem uwierzyć w niektóre odpowiedzi, które tutaj przeczytałem. W rzeczywistości istnieje instrukcja montażu, która robi to szybciej niż cokolwiek innego. bswap. Możesz po prostu napisać taką funkcję ...

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

Jest DUŻO szybszy niż sugerowane elementy wewnętrzne. Zdemontowałem je i spojrzałem. Powyższa funkcja nie ma prologu / epilogu, więc praktycznie nie ma w ogóle narzutu.

unsigned long _byteswap_ulong(unsigned long value);

Wykonanie 16 bitów jest równie łatwe, z wyjątkiem tego, że użyłbyś xchg al, ah. bswap działa tylko na rejestrach 32-bitowych.

Wersja 64-bitowa jest nieco trudniejsza, ale nie przesadnie. Znacznie lepiej niż wszystkie powyższe przykłady z pętlami i szablonami itp.

Jest tu kilka ostrzeżeń ... Po pierwsze, bswap jest dostępny tylko na procesorach 80x486 i wyższych. Czy ktoś planuje uruchomić go na 386?!? Jeśli tak, nadal możesz zamienić bswap na ...

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

Również wbudowany zestaw jest dostępny tylko w kodzie x86 w Visual Studio. Naga funkcja nie może być wyłożona, a także nie jest dostępna w kompilacjach x64. W tym przypadku będziesz musiał użyć funkcji kompilatora.


1
_byteswap_ulongoraz _uint64(np. w zaakceptowanej odpowiedzi) oba kompilują się w celu użycia bswapinstrukcji. Byłbym zaskoczony, ale chciałbym wiedzieć, czy ten asm jest o wiele szybszy, ponieważ pomija jedynie prolog / epilog - czy porównałeś go?
ZachB

@stdcall Pytanie nie wymagało przenośnego rozwiązania ani nawet nie wspominało o platformie. Jak powiedziała moja odpowiedź, powyższe informacje dotyczą najszybszego sposobu zamiany endianów. Jasne, jeśli piszesz to na platformie innej niż X86, to nie zadziała, ale jak już wspomniałem, jesteś ograniczony do wewnętrznych elementów kompilatora, jeśli Twój kompilator je obsługuje.
Spawacz

@ZachB W tym konkretnym przypadku myślę, że pominięcie prologu i epilogu da ci przyzwoitą oszczędność, ponieważ zasadniczo wykonujesz tylko 1 instrukcję. Prolog będzie musiał wcisnąć na stos, zrobić odejmowanie, ustawić wskaźnik bazy, a następnie podobnie na końcu. Nie przeprowadziłem testów porównawczych, ale powyższy ma łańcuch zależności 0, którego po prostu nie dostaniesz bez nagości. Może dobry kompilator by to wstawił, ale wtedy jesteś w innym parku.
Spawacz

2
Być może. Zauważ jednak, że w typowym przypadku zamiany tablicy liczb, elementy kompilatora omówione w innych odpowiedziach wykorzystają rozszerzenia SSE / AVX i wyemitują PSHUFB, który przewyższa BSWAP. Zobacz wm.ite.pl/articles/reverse-array-of-bytes.html
ZachB

Szkoda, że ​​IMHO publikuje rozwiązanie specyficzne dla platformy, gdy OP nie określił, że potrzebuje rozwiązania tylko dla x86. I aby zdyskredytować inne rozwiązania, gdy twoje nie nadaje się do użytku w wielu powszechnie używanych systemach operacyjnych, takich jak iOS i Android (które używają procesorów ARM lub MIPS).
Jens Alfke

0

Przenośna technika wdrażania przyjaznych optymalizatorowi nieprzystosowanych niewymienionych akcesoriów endian. Działają na każdym kompilatorze, każdym wyrównaniu granic i każdym zamówieniu bajtów. Te niewyrównane procedury są uzupełniane lub poruszane, w zależności od rodzimego endianu i wyrównania. Częściowa lista, ale masz pomysł. BO * są stałymi wartościami opartymi na natywnej kolejności bajtów.

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];
}

Te typy typef mają tę zaletę, że podnoszą błędy kompilatora, jeśli nie są używane z akcesoriami, dzięki czemu łagodzą błędy zapomnianych akcesoriów.

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

2
W przypadku tego pytania znacznik C ++ robi różnicę. Istnieje wiele niezdefiniowanych zachowań z powodu C ++ i unii.
jww

0

Oto jak odczytać podwójne zapisane w 64-bitowym formacie IEEE 754, nawet jeśli komputer-host używa innego systemu.

/*
* 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;
    }
}

Resztę zestawu funkcji, w tym procedury zapisu i liczby całkowite, znajdziesz w moim projekcie github

https://github.com/MalcolmMcLean/ieee754


0

Zamiana bajtów przy użyciu starej sztuczki z 3 krokami xor wokół osi przestawnej w funkcji szablonu daje elastyczne, szybkie rozwiązanie O (ln2), które nie wymaga biblioteki, styl tutaj odrzuca również typy 1-bajtowe:

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);
    }
}

0

Wydaje się, że bezpiecznym sposobem byłoby użycie htonów na każdym słowie. Więc jeśli masz ...

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); });

Powyższe byłoby brakiem operacji, jeśli korzystasz z systemu big-endian, więc szukałbym wszystkiego, czego używa twoja platforma jako warunku czasu kompilacji, aby zdecydować, czy htons nie ma opcji. W końcu to O (n). Na komputerze Mac byłoby to jak ...

#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

0

Jeśli masz C ++ 17, dodaj ten nagłówek

#include <algorithm>

Użyj tej funkcji szablonu, aby zamienić bajty:

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);
}

nazwij to tak:

swapEndian (stlContainer);

-4

Spójrz w górę, trochę się zmienia, ponieważ jest to w zasadzie wszystko, co musisz zrobić, aby zamienić z małego -> dużego endiana. Następnie, w zależności od rozmiaru bitu, zmieniasz sposób przesuwania bitu.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.