Muszę napisać funkcję do konwersji big endian na little endian w C. Nie mogę używać żadnej funkcji biblioteki.
c
swap
endianness
Alex Xander
źródło
źródło
Odpowiedzi:
Zakładając, że potrzebujesz prostej zamiany bajtów, spróbuj czegoś takiego
Unsigned 16-bitowa konwersja:
swapped = (num>>8) | (num<<8);
Niepodpisana konwersja 32-bitowa:
swapped = ((num>>24)&0xff) | // move byte 3 to byte 0 ((num<<8)&0xff0000) | // move byte 1 to byte 2 ((num>>8)&0xff00) | // move byte 2 to byte 1 ((num<<24)&0xff000000); // byte 0 to byte 3
Spowoduje to zamianę kolejności bajtów z pozycji 1234 na 4321. Jeśli wprowadzono wartość
0xdeadbeef
, 32-bitowa zamiana endian mogłaby dać wynik0xefbeadde
.Powyższy kod powinien zostać wyczyszczony za pomocą makr lub przynajmniej stałych zamiast magicznych liczb, ale miejmy nadzieję, że pomoże tak, jak jest
EDYCJA: jak wskazała inna odpowiedź, istnieją alternatywy specyficzne dla platformy, systemu operacyjnego i zestawu instrukcji, które mogą być DUŻO szybsze niż powyższe. W jądrze Linuksa są makra (na przykład cpu_to_be32), które całkiem dobrze obsługują endianness. Ale te alternatywy są specyficzne dla ich środowisk. W praktyce z endianizmem najlepiej radzić sobie stosując połączenie dostępnych podejść
źródło
((num & 0xff) >> 8) | (num << 8)
, gcc 4.8.3 generuje pojedyncząrol
instrukcję. A jeśli 32-bitowa konwersja jest zapisana jako((num & 0xff000000) >> 24) | ((num & 0x00ff0000) >> 8) | ((num & 0x0000ff00) << 8) | (num << 24)
, ten sam kompilator generuje pojedyncząbswap
instrukcję.struct byte_t reverse(struct byte_t b) { struct byte_t rev; rev.ba = b.bh; rev.bb = b.bg; rev.bc = b.bf; rev.bd = b.be; rev.be = b.bd; rev.bf = b.bc; rev.bg = b.bb; rev.bh = b.ba; return rev;}
pola bitów : gdzie jest to pole bitowe z 8 polami po 1 bit. Ale nie jestem pewien, czy to tak szybko, jak inne sugestie. W przypadku liczb całkowitych użyjunion { int i; byte_t[sizeof(int)]; }
do odwrócenia bajtu po bajcie w liczbie całkowitej.Włączając:
#include <byteswap.h>
można uzyskać zoptymalizowaną wersję zależnych od maszyny funkcji zamiany bajtów. Następnie możesz łatwo korzystać z następujących funkcji:
__bswap_32 (uint32_t input)
lub
__bswap_16 (uint16_t input)
źródło
#include <byteswap.h>
, zobacz komentarz w samym pliku .h. Ten post zawiera przydatne informacje, więc zagłosowałem w górę, mimo że autor zignorował wymóg OP, aby nie używać funkcji lib.#include <stdint.h> //! Byte swap unsigned short uint16_t swap_uint16( uint16_t val ) { return (val << 8) | (val >> 8 ); } //! Byte swap short int16_t swap_int16( int16_t val ) { return (val << 8) | ((val >> 8) & 0xFF); } //! Byte swap unsigned int uint32_t swap_uint32( uint32_t val ) { val = ((val << 8) & 0xFF00FF00 ) | ((val >> 8) & 0xFF00FF ); return (val << 16) | (val >> 16); } //! Byte swap int int32_t swap_int32( int32_t val ) { val = ((val << 8) & 0xFF00FF00) | ((val >> 8) & 0xFF00FF ); return (val << 16) | ((val >> 16) & 0xFFFF); }
Aktualizacja : Dodano wymianę 64-bitowych bajtów
int64_t swap_int64( int64_t val ) { val = ((val << 8) & 0xFF00FF00FF00FF00ULL ) | ((val >> 8) & 0x00FF00FF00FF00FFULL ); val = ((val << 16) & 0xFFFF0000FFFF0000ULL ) | ((val >> 16) & 0x0000FFFF0000FFFFULL ); return (val << 32) | ((val >> 32) & 0xFFFFFFFFULL); } uint64_t swap_uint64( uint64_t val ) { val = ((val << 8) & 0xFF00FF00FF00FF00ULL ) | ((val >> 8) & 0x00FF00FF00FF00FFULL ); val = ((val << 16) & 0xFFFF0000FFFF0000ULL ) | ((val >> 16) & 0x0000FFFF0000FFFFULL ); return (val << 32) | (val >> 32); }
źródło
int32_t
iint64_t
wariantów, jakie jest uzasadnienie maskowania... & 0xFFFF
i... & 0xFFFFFFFFULL
? Czy coś się dzieje z rozszerzeniem znaku, którego nie widzę? Dlaczegoswap_int64
wracauint64_t
? Nie powinno tak byćint64_t
?swap_int64
w swojej odpowiedzi. +1 za pomocną odpowiedź, BTW!LL
Są niepotrzebne w(u)swap_uint64()
podobnie do konstrukcjiL
nie jest potrzebna(u)swap_uint32()
. NieU
jest potrzebny,uswap_uint64()
podobnie jakU
nie jest potrzebnyuswap_uint32()
Oto dość ogólna wersja; Nie skompilowałem tego, więc prawdopodobnie są literówki, ale powinieneś mieć pomysł,
void SwapBytes(void *pv, size_t n) { assert(n > 0); char *p = pv; size_t lo, hi; for(lo=0, hi=n-1; hi>lo; lo++, hi--) { char tmp=p[lo]; p[lo] = p[hi]; p[hi] = tmp; } } #define SWAP(x) SwapBytes(&x, sizeof(x));
Uwaga: nie jest to zoptymalizowane pod kątem szybkości ani przestrzeni. Ma być przejrzysty (łatwy do debugowania) i przenośny.
Aktualizacja 2018-04-04 Dodano funkcję assert (), aby przechwytywać nieprawidłowy przypadek n == 0, jak zauważył komentator @chux.
źródło
bswap
instrukcji przez przyzwoity kompilator X86 z włączoną optymalizacją. Ta wersja z parametrem rozmiaru nie mogła tego zrobić.Jeśli potrzebujesz makr (np. System wbudowany):
#define SWAP_UINT16(x) (((x) >> 8) | ((x) << 8)) #define SWAP_UINT32(x) (((x) >> 24) | (((x) & 0x00FF0000) >> 8) | (((x) & 0x0000FF00) << 8) | ((x) << 24))
źródło
UINT
w ich imieniu jest.Edycja: są to funkcje biblioteczne. Postępowanie zgodnie z nimi jest sposobem ręcznym.
Jestem absolutnie oszołomiony liczbą osób nieświadomych __byteswap_ushort, __byteswap_ulong i __byteswap_uint64 . Oczywiście, są one specyficzne dla Visual C ++, ale kompilują się do pysznego kodu na architekturach x86 / IA-64. :)
Oto wyraźne użycie
bswap
instrukcji pobranej z tej strony . Zauważ, że powyższa wewnętrzna forma zawsze będzie szybsza niż ta , dodałem ją tylko po to, aby udzielić odpowiedzi bez procedury bibliotecznej.uint32 cq_ntohl(uint32 a) { __asm{ mov eax, a; bswap eax; } }
źródło
Jako żart:
#include <stdio.h> int main (int argc, char *argv[]) { size_t sizeofInt = sizeof (int); int i; union { int x; char c[sizeof (int)]; } original, swapped; original.x = 0x12345678; for (i = 0; i < sizeofInt; i++) swapped.c[sizeofInt - i - 1] = original.c[i]; fprintf (stderr, "%x\n", swapped.x); return 0; }
źródło
int i, size_t sizeofInt
i nie ten sam typ w obu przypadkach.oto sposób użycia instrukcji SSSE3 pshufb przy użyciu jej wewnętrznej funkcji Intel, zakładając, że masz wielokrotność 4
int
s:unsigned int *bswap(unsigned int *destination, unsigned int *source, int length) { int i; __m128i mask = _mm_set_epi8(12, 13, 14, 15, 8, 9, 10, 11, 4, 5, 6, 7, 0, 1, 2, 3); for (i = 0; i < length; i += 4) { _mm_storeu_si128((__m128i *)&destination[i], _mm_shuffle_epi8(_mm_loadu_si128((__m128i *)&source[i]), mask)); } return destination; }
źródło
Czy to zadziała / będzie szybsze?
uint32_t swapped, result; ((byte*)&swapped)[0] = ((byte*)&result)[3]; ((byte*)&swapped)[1] = ((byte*)&result)[2]; ((byte*)&swapped)[2] = ((byte*)&result)[1]; ((byte*)&swapped)[3] = ((byte*)&result)[0];
źródło
char
niebyte
.Oto funkcja, której używałem - przetestowana i działa na każdym podstawowym typie danych:
// SwapBytes.h // // Function to perform in-place endian conversion of basic types // // Usage: // // double d; // SwapBytes(&d, sizeof(d)); // inline void SwapBytes(void *source, int size) { typedef unsigned char TwoBytes[2]; typedef unsigned char FourBytes[4]; typedef unsigned char EightBytes[8]; unsigned char temp; if(size == 2) { TwoBytes *src = (TwoBytes *)source; temp = (*src)[0]; (*src)[0] = (*src)[1]; (*src)[1] = temp; return; } if(size == 4) { FourBytes *src = (FourBytes *)source; temp = (*src)[0]; (*src)[0] = (*src)[3]; (*src)[3] = temp; temp = (*src)[1]; (*src)[1] = (*src)[2]; (*src)[2] = temp; return; } if(size == 8) { EightBytes *src = (EightBytes *)source; temp = (*src)[0]; (*src)[0] = (*src)[7]; (*src)[7] = temp; temp = (*src)[1]; (*src)[1] = (*src)[6]; (*src)[6] = temp; temp = (*src)[2]; (*src)[2] = (*src)[5]; (*src)[5] = temp; temp = (*src)[3]; (*src)[3] = (*src)[4]; (*src)[4] = temp; return; } }
źródło
source
jest dostosowywany w razie potrzeby - ale jeśli to założenie nie jest spełnione, kod to UB.EDYCJA: Ta funkcja zamienia tylko endianness wyrównanych 16-bitowych słów. Funkcja często potrzebna do kodowania UTF-16 / UCS-2. EDYTUJ KONIEC.
Jeśli chcesz zmienić endianness bloku pamięci, możesz użyć mojego niesamowicie szybkiego podejścia. Twoja tablica pamięci powinna mieć rozmiar będący wielokrotnością 8.
#include <stddef.h> #include <limits.h> #include <stdint.h> void ChangeMemEndianness(uint64_t *mem, size_t size) { uint64_t m1 = 0xFF00FF00FF00FF00ULL, m2 = m1 >> CHAR_BIT; size = (size + (sizeof (uint64_t) - 1)) / sizeof (uint64_t); for(; size; size--, mem++) *mem = ((*mem & m1) >> CHAR_BIT) | ((*mem & m2) << CHAR_BIT); }
Ten rodzaj funkcji jest przydatny do zmiany endianess plików Unicode UCS-2 / UTF-16.
źródło
t know if it
tak szybki jak sugestie, ale to wokrsCHAR_BIT
zamiast8
jest ciekawy, od którego0xFF00FF00FF00FF00ULL
zależyCHAR_BIT == 8
. Zauważ, żeLL
nie jest potrzebne w stałej.CHAR_BIT
celu zwiększenia ekspozycji tego makra. Jeśli chodzi o LL, to bardziej adnotacja niż cokolwiek innego. Jest to również nawyk, który wyłapałem od dawna z błędnymi kompilatorami (przed standardem), które nie działałyby dobrze.Ten fragment kodu może konwertować 32-bitową małą liczbę Endian na liczbę Big Endian.
#include <stdio.h> main(){ unsigned int i = 0xfafbfcfd; unsigned int j; j= ((i&0xff000000)>>24)| ((i&0xff0000)>>8) | ((i&0xff00)<<8) | ((i&0xff)<<24); printf("unsigned int j = %x\n ", j); }
źródło
((i>>24)&0xff) | ((i>>8)&0xff00) | ((i&0xff00)<<8) | (i<<24);
może być szybsze na niektórych platformach (np. Recykling stałych maski AND). Jednak większość kompilatorów zrobiłaby to, ale niektóre proste kompilatory nie są w stanie zoptymalizować tego za Ciebie.Jeśli używasz procesora x86 lub x86_64, big endian jest natywny. więc
dla wartości 16-bitowych
unsigned short wBigE = value; unsigned short wLittleE = ((wBigE & 0xFF) << 8) | (wBigE >> 8);
dla wartości 32-bitowych
unsigned int iBigE = value; unsigned int iLittleE = ((iBigE & 0xFF) << 24) | ((iBigE & 0xFF00) << 8) | ((iBigE >> 8) & 0xFF00) | (iBigE >> 24);
Nie jest to najbardziej wydajne rozwiązanie, chyba że kompilator rozpozna, że jest to manipulacja na poziomie bajtów i generuje kod wymiany bajtów. Ale nie zależy od żadnych sztuczek dotyczących układu pamięci i można go dość łatwo przekształcić w makro.
źródło