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.


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


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)

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

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.


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

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

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

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

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

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

}

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.


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

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.


25
Na architekturach x86 i x86_64 schemat little endian jest schematem natywnym.
— MK aka Grisu
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.