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.
c++
endianness
Uhall
źródło
źródło
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.Odpowiedzi:
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:
Dla liczb 32-bitowych:
Dla liczb 64-bitowych:
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 :
(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 ..
źródło
__builtin_bswapX
jest dostępna tylko od GCC-4.3 r.htonl
,htons
itp Trzeba wiedzieć z kontekstu sytuacji, gdy rzeczywiście zamienić bajtów.htonl
intohl
bez 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ć nahtonl
intohl
.Po prostu:
Zastosowanie:
swap_endian<uint32_t>(42)
.źródło
From The Byte Order Fallacy autorstwa Rob Pike:
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
data
była tablicąunsigned char
lubuint8_t
. Użyciesigned char
lubchar
(jeśli podpisane) spowodujedata[x]
awans na liczbę całkowitą idata[x] << 24
potencjalnie przesunięcie 1 do bitu znaku, który jest UB.źródło
Jeśli robisz to dla celów kompatybilności sieci / hosta, powinieneś użyć:
Jeśli robisz to z innego powodu, jedno z przedstawionych tutaj rozwiązań byte_swap będzie działać dobrze.
źródło
htonl
intohl
nie można przejść do małego endiana na platformie big-endian.Wziąłem kilka sugestii z tego postu i złożyłem je razem, aby utworzyć:
źródło
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:
źródło
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.źródło
Zrobiliśmy to za pomocą szablonów. Możesz zrobić coś takiego:
źródło
Jeśli robisz to, aby przesyłać dane między różnymi platformami, spójrz na funkcje ntoh i hton.
źródło
Tak samo jak w C:
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.
źródło
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:
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.
źródło
htonl
i 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.)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:
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}.
źródło
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 ++:Wynik:
źródło
podoba mi się ten, tylko dla stylu :-)
źródło
char[]
Pojawia się komunikat o błędzie „Błąd: niedokończony typ jest niedozwolony”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 ????
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 jakouint8_t
. Dlatego właściwą rzeczą jest skopiowanie tego do tablicy char, zamiana, a następnie skopiowanie z powrotem (więc niereinterpret_cast
). Zauważ, że kompilatory są w większości wystarczająco inteligentne, aby przekonwertować to, co zrobiłeś z powrotem na,reinterpret_cast
jeśli są w stanie uzyskać dostęp do poszczególnych bajtów niezależnie od wyrównania.Aby użyć tej funkcji :
a teraz
x
różni się endianizmem.źródło
new
/delete
do 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.for(size_t i = 0 ; i < sizeof(var) ; i++)
zamiaststatic_cast<long>
. (Lub właściwie zamiana w miejscu użyje rosnącej i malejącej,char*
więc i tak zniknie).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:
źródło
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 ->
źródło
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.
Oświadczenie: Nie próbowałem tego kompilować ani testować.
źródło
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:
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:
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:
Jeśli chcesz, możesz napisać to w pętli. To nie będzie wydajne, ale dla zabawy:
A dla kompletności, oto uproszczona 32-bitowa wersja pierwszego formularza:
źródło
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.
źródło
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.
źródło
Korzystając z poniższych kodów, możesz łatwo przełączać się między BigEndian i LittleEndian
źródło
Niedawno napisałem makro, aby to zrobić w C, ale jest równie poprawne w C ++:
Akceptuje dowolny typ i odwraca bajty w przekazanym argumencie. Przykładowe zastosowania:
Które wydruki:
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_BYTES
jakfor
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
for
pętli są dwa bajty, do których się odwołujemy, i zamiana XOR (więc nazwa zmiennej tymczasowej nie jest wymagana):__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 dounsigned char
wskaź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 ++.
źródło
__VA_ARGS__
?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ę ...
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.
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 ...
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.
źródło
_byteswap_ulong
oraz_uint64
(np. w zaakceptowanej odpowiedzi) oba kompilują się w celu użyciabswap
instrukcji. Byłbym zaskoczony, ale chciałbym wiedzieć, czy ten asm jest o wiele szybszy, ponieważ pomija jedynie prolog / epilog - czy porównałeś go?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.
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.
źródło
Oto jak odczytać podwójne zapisane w 64-bitowym formacie IEEE 754, nawet jeśli komputer-host używa innego systemu.
Resztę zestawu funkcji, w tym procedury zapisu i liczby całkowite, znajdziesz w moim projekcie github
https://github.com/MalcolmMcLean/ieee754
źródło
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:
źródło
Wydaje się, że bezpiecznym sposobem byłoby użycie htonów na każdym słowie. Więc jeśli masz ...
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 ...
źródło
Jeśli masz C ++ 17, dodaj ten nagłówek
Użyj tej funkcji szablonu, aby zamienić bajty:
nazwij to tak:
źródło
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.
źródło