konwertuj big endian na little endian w C [bez użycia podanej funkcji] [zamknięte]

92

Muszę napisać funkcję do konwersji big endian na little endian w C. Nie mogę używać żadnej funkcji biblioteki.

Alex Xander
źródło
5
wartość 16-bitowa? Wartość 32-bitowa? pływak? tablica?
John Knoeller
20
czas wybrać odpowiedź?
Aniket Inge
7
Głosowanie za ponownym otwarciem. To samo, co stackoverflow.com/questions/105252/… dla C ++. Moglibyśmy po prostu edytować, aby było jaśniejsze.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

Odpowiedzi:

172

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ć wynik 0xefbeadde.

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

Sam Post
źródło
5
+1 za wzmiankę o metodach specyficznych dla platformy / sprzętu. Programy są zawsze uruchamiane na jakimś sprzęcie, a funkcje sprzętowe są zawsze najszybsze.
eonil
21
jeśli konwersja 16-bitowa jest wykonywana jako ((num & 0xff) >> 8) | (num << 8), gcc 4.8.3 generuje pojedynczą rolinstrukcję. 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ą bswapinstrukcję.
user666412
Nie wiem, jak wydajne jest to, ale zamieniłem kolejność bajtów na takie 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żyj union { int i; byte_t[sizeof(int)]; }do odwrócenia bajtu po bajcie w liczbie całkowitej.
Ilian Zapryanov
Myślę, że wyrażenie musi wyglądać następująco: (num >> 8) | (num << 8), aby odwrócić kolejność bajtów i NIE: ((num & 0xff) >> 8) | (num << 8), Zły przykład daje zero w młodszym bajcie.
jscom
@IlianZapryanov Może +1 dla jasności, ale używanie pól bitowych w C jest prawdopodobnie najmniej wydajnym sposobem na zrobienie tego.
sherrellbc
105

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)
Amir Mgh
źródło
3
Dziękuję za odpowiedź, ale nie mogę użyć żadnej funkcji biblioteki
Mark Ransom
4
Powinien przeczytać #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.
Eli Rosencruft
30
W rzeczywistości funkcje __bswap_32 / __ bswap_16 są w rzeczywistości makrami, a nie funkcjami bibliotecznymi, co jest kolejnym powodem do głosowania pozytywnego.
Eli Rosencruft
7
Rozumiem, że ten nagłówek nie jest gwarantowany dla wszystkich systemów operacyjnych na wszystkich architekturach. Nie znalazłem jeszcze przenośnego sposobu radzenia sobie z problemami endian.
Edward Falk
2
nie istnieje w systemie Windows - przynajmniej nie podczas kompilacji krzyżowej z Linuksa z 32- lub 64-bitową wersją mingw
bph
61
#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);
}
chmike
źródło
Dla int32_ti int64_twariantów, jakie jest uzasadnienie maskowania ... & 0xFFFFi ... & 0xFFFFFFFFULL? Czy coś się dzieje z rozszerzeniem znaku, którego nie widzę? Dlaczego swap_int64wraca uint64_t? Nie powinno tak być int64_t?
bgoodr
1
Swap_int64 zwracający uint64 jest rzeczywiście błędem. Maskowanie wartościami int ze znakiem faktycznie ma na celu usunięcie znaku. Przesunięcie w prawo wstrzykuje bit znaku po lewej stronie. Moglibyśmy tego uniknąć, po prostu wywołując operację zamiany unsigned int.
chmike
Dzięki. Możesz chcieć zmienić typ wartości zwracanej swap_int64w swojej odpowiedzi. +1 za pomocną odpowiedź, BTW!
bgoodr
Czy bitowe i wartości endian są zależne?
MarcusJ
1
LLSą niepotrzebne w (u)swap_uint64()podobnie do konstrukcji Lnie jest potrzebna (u)swap_uint32(). Nie Ujest potrzebny, uswap_uint64()podobnie jak Unie jest potrzebnyuswap_uint32()
chux - Przywróć Monikę
13

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.

Michael J.
źródło
1
możesz użyć xorSwap dla lepszej wydajności. Preferuj tę ogólną wersję ponad wszystkie wersje specyficzne dla rozmiaru ...
Przetestowałem to, okazuje się, że jest szybszy niż xorSwap ... na x86. stackoverflow.com/questions/3128095/…
1
@nus - Jedną z zalet bardzo prostego kodu jest to, że optymalizator kompilatora może czasami zrobić to bardzo szybko.
Michael J
@MichaelJ OTOH, 32-bitowa wersja powyżej w odpowiedzi chmike zostaje skompilowana do pojedynczej bswapinstrukcji przez przyzwoity kompilator X86 z włączoną optymalizacją. Ta wersja z parametrem rozmiaru nie mogła tego zrobić.
Alnitak
@Alnitak - Jak powiedziałem, nie podjąłem żadnych wysiłków, aby zoptymalizować mój kod. Kiedy użytkownik nus stwierdził, że kod działa bardzo szybko (w jednym przypadku) wspomniałem tylko o ogólnym pomyśle, że prosty kod często może być wysoce zoptymalizowany przez kompilator. Mój kod działa w wielu różnych przypadkach i jest dość łatwy do zrozumienia, a przez to łatwy do debugowania. To spełniło moje cele.
Michael J
9

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))
kol
źródło
Te makra są w porządku, ale ((x) >> 24) nie powiedzie się, gdy liczba całkowita ze znakiem jest między 0x80000000 a 0xffffffff. Dobrym pomysłem jest tutaj użycie bitowego AND. Uwaga: ((x) << 24) jest całkowicie bezpieczne. (x) >> 8) również nie powiedzie się, jeśli 16-bitowe wysokie wartości są różne od zera (lub podano 16-bitową wartość ze znakiem).
2
@ PacMan - te makra są przeznaczone do zamiany wyłącznie liczb całkowitych bez znaku . Dlatego UINTw ich imieniu jest.
kol
Tak, prawda, przepraszam za hałas. Czy nie byłoby najlepiej osadzić typecast?
5

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 bswapinstrukcji 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; 
    }
}
Sam Harwell
źródło
21
Jeśli chodzi o pytanie w języku C, sugerujesz coś, co jest specyficzne dla języka Visual C ++?
Alok Singhal
3
@Alok: Visual C ++ to produkt firmy Microsoft. Działa dobrze do kompilowania kodu C. :)
Sam Harwell
20
Dlaczego zaskakuje Cię fakt, że wiele osób nie jest świadomych implementacji zamiany bajtów specyficznych dla firmy Microsoft?
dreamlax
36
Fajnie, to dobra informacja dla każdego, kto opracowuje produkt o zamkniętym kodzie źródłowym, który nie musi być przenośny ani zgodny ze standardami.
Sam Post
6
@Alok, OP nie wspomniał o kompilatorze | OS. Osoba może udzielić odpowiedzi zgodnie ze swoim doświadczeniem z określonym zestawem narzędzi.
Aniket Inge
5

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;
}
dreamlax
źródło
7
HAHAHAHAHA. Hahaha. Ha. Ha? (Jaki żart?)
3
czy wyciągnąłeś to z jakiegoś repozytorium źródłowego Windows? :)
hochl
Nodejs używa tej techniki! github.com/nodejs/node/blob/…
Justin Moser
Ciekawy w użyciu int i, size_t sizeofInti nie ten sam typ w obu przypadkach.
chux - Przywróć Monikę
5

oto sposób użycia instrukcji SSSE3 pshufb przy użyciu jej wewnętrznej funkcji Intel, zakładając, że masz wielokrotność 4 ints:

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;
}
jcomeau_ictx
źródło
3

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];
Paweł
źródło
2
Myślę, że charnie byte.
dreamlax
Korzystając z tej strategii, rozwiązanie z największą liczbą głosów w porównaniu do Twojego jest równoważne, najbardziej wydajne i przenośne. Jednak rozwiązanie, które proponuję (drugie miejsce w głosowaniu) wymaga mniej operacji i powinno być bardziej wydajne.
chmike
1

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

}
biletowiec
źródło
2
Kod opiera się na bardzo rozsądnym założeniu: sourcejest dostosowywany w razie potrzeby - ale jeśli to założenie nie jest spełnione, kod to UB.
chux - Przywróć Monikę
1

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.

Patrick Schlüter
źródło
CHAR_BIT #define brakuje, aby kod był kompletny.
Tõnu Samuel
Ok, dodałem brakujące elementy.
Patrick Schlüter
tutaj jest link do wymiany w C ++, nie jestem t know if ittak szybki jak sugestie, ale to wokrs
Ilian Zapryanov
CHAR_BITzamiast 8jest ciekawy, od którego 0xFF00FF00FF00FF00ULLzależy CHAR_BIT == 8. Zauważ, że LLnie jest potrzebne w stałej.
chux - Przywróć Monikę
Masz rację chux. Napisałem tylko w CHAR_BITcelu 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.
Patrick Schlüter
1

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);    
}
Kaushal Billore
źródło
Dzięki @YuHao Jestem tutaj nowy, nie wiem, jak sformatować tekst.
Kaushal Billore
2
Użycie ((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.
-7

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.

John Knoeller
źródło
25
Na architekturach x86 i x86_64 schemat little endian jest schematem natywnym.
MK aka Grisu