converti big endian in little endian in C [senza usare la funzione fornita] [chiuso]


91

Ho bisogno di scrivere una funzione per convertire big endian in little endian in C. Non posso usare nessuna funzione di libreria.


5
un valore a 16 bit? Valore a 32 bit? galleggiante? un array?
John Knoeller

19
tempo per scegliere una risposta forse?
Aniket Inge

7
Votazioni per riaprire. Come stackoverflow.com/questions/105252/… per C ++. Potremmo semplicemente modificare per renderlo più chiaro.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

Risposte:


168

Supponendo che ciò di cui hai bisogno sia un semplice scambio di byte, prova qualcosa di simile

Conversione a 16 bit senza segno:

swapped = (num>>8) | (num<<8);

Conversione a 32 bit senza segno:

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

Questo scambia gli ordini di byte dalle posizioni 1234 a 4321. Se il tuo input fosse 0xdeadbeef, uno swap endian a 32 bit potrebbe avere un output di 0xefbeadde.

Il codice sopra dovrebbe essere ripulito con macro o almeno costanti invece di numeri magici, ma si spera che aiuti così com'è

EDIT: come ha sottolineato un'altra risposta, ci sono alternative specifiche per piattaforma, sistema operativo e set di istruzioni che possono essere MOLTO più veloci di quanto sopra. Nel kernel Linux ci sono delle macro (cpu_to_be32 per esempio) che gestiscono abbastanza bene l'endianness. Ma queste alternative sono specifiche per i loro ambienti. In pratica, il modo migliore per affrontare l'endianness è utilizzare una combinazione di approcci disponibili


5
+1 per la menzione di metodi specifici per piattaforma / hardware. I programmi vengono sempre eseguiti su alcuni componenti hardware e le funzionalità hardware sono sempre più veloci.
eonil

21
se la conversione a 16 bit viene eseguita come ((num & 0xff) >> 8) | (num << 8), gcc 4.8.3 genera una singola rolistruzione. E se la conversione a 32 bit viene scritta come ((num & 0xff000000) >> 24) | ((num & 0x00ff0000) >> 8) | ((num & 0x0000ff00) << 8) | (num << 24), lo stesso compilatore genera una singola bswapistruzione.
user666412

Non so quanto sia efficiente, ma ho scambiato l'ordine dei byte 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;}con campi di bit come questo: dove questo è un campo di bit con 8 campi da 1 bit ciascuno. Ma non sono sicuro che sia veloce come gli altri suggerimenti. Per gli int utilizzare il union { int i; byte_t[sizeof(int)]; }per invertire byte per byte nell'intero.
Ilian Zapryanov

Penso che l'espressione debba essere: (num >> 8) | (num << 8) per invertire l'ordine dei byte e NOT: ((num & 0xff) >> 8) | (num << 8), L'esempio sbagliato ottiene zero nel byte basso.
jscom

@IlianZapryanov Forse +1 per chiarezza, ma usare campi di bit in C in questo modo è probabilmente il modo meno efficiente per farlo.
Sherrellbc

104

Includendo:

#include <byteswap.h>

è possibile ottenere una versione ottimizzata delle funzioni di scambio di byte dipendenti dalla macchina. Quindi, puoi facilmente utilizzare le seguenti funzioni:

__bswap_32 (uint32_t input)

o

__bswap_16 (uint16_t input)

3
Grazie per la tua risposta, ma non posso utilizzare alcuna funzione di libreria
Mark Ransom

4
Dovrebbe essere letto #include <byteswap.h>, vedere il commento nel file .h stesso. Questo post contiene informazioni utili, quindi ho votato in alto nonostante l'autore ignori il requisito OP di non utilizzare una funzione lib.
Eli Rosencruft

30
In effetti, le funzioni __bswap_32 / __ bswap_16 sono in realtà macro e non funzioni di libreria, un altro motivo per votare.
Eli Rosencruft

7
La mia comprensione è che non è garantito che questa intestazione esista per tutti i sistemi operativi su tutte le architetture. Devo ancora trovare un modo portatile per affrontare i problemi di Endian.
Edward Falk

2
non esiste su Windows - almeno non quando si esegue la compilazione incrociata da Linux con mingw 32 o 64 bit
bph

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

Aggiornamento : aggiunto scambio di byte a 64 bit

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

Per le varianti int32_te int64_t, qual è il ragionamento alla base del mascheramento di ... & 0xFFFFe ... & 0xFFFFFFFFULL? C'è qualcosa che sta succedendo con l'estensione del segno qui che non vedo? Inoltre, perché sta swap_int64tornando uint64_t? Non dovrebbe essere quello int64_t?
bgoodr

1
Lo swap_int64 che restituisce un uint64 è davvero un errore. Il mascheramento con valori int con segno serve infatti a rimuovere il segno. Lo spostamento a destra inietta il bit di segno a sinistra. Potremmo evitarlo semplicemente chiamando l'operazione di scambio int senza segno.
Chmike

Grazie. Potresti voler cambiare il tipo di valore restituito per swap_int64nella tua risposta. +1 per la risposta utile, BTW!
bgoodr

L'endian bit per bit e il valore dipendono?
MarcusJ

1
Non LLsono necessari in modo (u)swap_uint64()molto simile a un Lnon necessario in (u)swap_uint32(). Non Uè necessario uswap_uint64()molto come Unon è necessario inuswap_uint32()
chux - Ripristina Monica il

13

Ecco una versione abbastanza generica; Non l'ho compilato, quindi probabilmente ci sono errori di battitura, ma dovresti farti un'idea,

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

NB: non èottimizzato per la velocità o lo spazio. È pensato per essere chiaro (facile da eseguire il debug) e portabile.

Aggiornamento 2018-04-04 Aggiunto assert () per intercettare il caso non valido di n == 0, come notato dal commentatore @chux.


1
puoi usare xorSwap per prestazioni migliori. Preferisci questa versione generica a tutte quelle specifiche per taglia ...

L'ho testato, risulta che è più veloce di xorSwap ... su x86. stackoverflow.com/questions/3128095/...

1
@nus - Uno dei vantaggi del codice molto semplice è che l'ottimizzatore del compilatore a volte può renderlo molto veloce.
Michael J

@MichaelJ OTOH, la versione a 32 bit sopra nella risposta di chmike viene compilata in una singola bswapistruzione da un compilatore X86 decente con l'ottimizzazione abilitata. Questa versione con un parametro per la dimensione non poteva farlo.
Alnitak

@ Alnitak - Come ho detto, non ho fatto alcuno sforzo per ottimizzare il mio codice. Quando l'utente nus ha scoperto che il codice è stato eseguito molto velocemente (in un caso) ho appena accennato all'idea generale che il codice semplice può spesso essere altamente ottimizzato da un compilatore. Il mio codice funziona per un'ampia varietà di casi ed è abbastanza facile da capire e quindi facile da eseguire il debug. Questo ha raggiunto i miei obiettivi.
Michael J

8

Se hai bisogno di macro (es. Sistema integrato):

#define SWAP_UINT16(x) (((x) >> 8) | ((x) << 8))
#define SWAP_UINT32(x) (((x) >> 24) | (((x) & 0x00FF0000) >> 8) | (((x) & 0x0000FF00) << 8) | ((x) << 24))

Queste macro vanno bene, ma ((x) >> 24) fallirà quando un intero con segno è compreso tra 0x80000000 e 0xffffffff. È una buona idea usare AND bit per bit qui. Nota: ((x) << 24) è perfettamente sicuro. (x) >> 8) fallirà anche se i 16 bit alti sono diversi da zero (o viene fornito un valore di 16 bit con segno).

2
@ PacMan: queste macro devono essere utilizzate per scambiare solo interi senza segno. Ecco perché c'è il UINTnel loro nome.
kol

Sì, vero, scusa per il rumore. Non sarebbe meglio incorporare un typecast?

5

Modifica: queste sono funzioni di libreria. Seguirli è il modo manuale per farlo.

Sono assolutamente sbalordito dal numero di persone ignare di __byteswap_ushort, __byteswap_ulong e __byteswap_uint64 . Certo sono specifici per Visual C ++, ma si compilano fino a ottenere un codice delizioso su architetture x86 / IA-64. :)

Ecco un uso esplicito bswapdell'istruzione, estratto da questa pagina . Nota che il modulo intrinseco sopra sarà sempre più veloce di questo , l'ho aggiunto solo per dare una risposta senza una routine di libreria.

uint32 cq_ntohl(uint32 a) {
    __asm{
        mov eax, a;
        bswap eax; 
    }
}

21
Per una domanda in C, stai suggerendo qualcosa di specifico per Visual C ++?
Alok Singhal

3
@Alok: Visual C ++ è un prodotto di Microsoft. Funziona perfettamente per la compilazione del codice C. :)
Sam Harwell

20
Perché ti stupisce il fatto che molte persone non siano a conoscenza delle implementazioni specifiche di Microsoft di byteswapping?
dreamlax

36
Fantastico, questa è una buona informazione per chiunque sviluppi un prodotto closed source che non deve essere portatile o conforme agli standard.
Sam Post

6
@Alok, OP non ha menzionato il compilatore | OS. Una persona è autorizzata a dare risposte in base alla sua esperienza con un particolare insieme di strumenti.
Aniket Inge

5

Per scherzo:


#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
HA HA HA HA HA. Hahaha. Ah. Ah? (Che scherzo?)

3
l'hai estratto da qualche repository di sorgenti di Windows? :)
hochl

Nodejs utilizza questa tecnica! github.com/nodejs/node/blob/…
Justin Moser

Curioso da usare int i, size_t sizeofInte non dello stesso tipo per entrambi.
chux - Ripristina Monica il

5

ecco un modo utilizzando l'istruzione SSSE3 pshufb usando il suo intrinseco Intel, supponendo che tu abbia un multiplo di 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

Funzionerà / sarà più veloce?

 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
Penso che tu voglia dire char, no byte.
dreamlax

Utilizzando questa strategia, la soluzione con più voti rispetto alla tua è equivalente e la più efficiente e portatile. Tuttavia la soluzione che propongo (la seconda più votata) necessita di meno operazioni e dovrebbe essere più efficiente.
chmike

1

Ecco una funzione che ho utilizzato, testata e funziona su qualsiasi tipo di dati di base:

//  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
Il codice si basa su un presupposto molto ragionevole: sourceè allineato secondo necessità, ma se tale presupposto non è valido, il codice è UB.
chux - Ripristina Monica il

1

EDIT: questa funzione scambia solo l'endianità delle parole a 16 bit allineate. Una funzione spesso necessaria per le codifiche UTF-16 / UCS-2. EDIT END.

Se vuoi cambiare l'endianità di un blocco di memoria puoi usare il mio approccio incredibilmente veloce. La tua matrice di memoria dovrebbe avere una dimensione multipla di 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);
}

Questo tipo di funzione è utile per modificare l'endianità dei file Unicode UCS-2 / UTF-16.


CHAR_BIT #define manca per completare il codice.
Tõnu Samuel

Ok, ho aggiunto gli include mancanti.
Patrick Schlüter

ecco un collegamento a uno scambio in C ++, non sono t know if itveloce quanto i suggerimenti ma funziona: github.com/heatblazer/helpers/blob/master/utils.h
Ilian Zapryanov

CHAR_BITinvece di 8è curioso in quanto 0xFF00FF00FF00FF00ULLdipende da CHAR_BIT == 8. Nota che LLnon è necessario nella costante.
chux - Ripristina Monica il

Hai ragione chux. Scritto solo con CHAR_BITper aumentare l'esposizione di quella macro. Per quanto riguarda il LL, è più un'annotazione che altro. È anche un'abitudine che ho preso molto tempo fa con compilatori buggy (pre standard) che non avrebbero funzionato correttamente.
Patrick Schlüter

1

Questo frammento di codice può convertire un numero Little Endian a 32 bit in un numero 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);    
}

Grazie @YuHao sono nuovo qui, non so come formattare il testo.
Kaushal Billore

2
L'utilizzo ((i>>24)&0xff) | ((i>>8)&0xff00) | ((i&0xff00)<<8) | (i<<24);potrebbe essere più veloce su alcune piattaforme (ad esempio riciclando le costanti della maschera AND). La maggior parte dei compilatori lo farebbe, tuttavia, ma alcuni semplici compilatori non sono in grado di ottimizzarlo per te.

-7

Se stai utilizzando un processore x86 o x86_64, il big endian è nativo. così

per valori a 16 bit

unsigned short wBigE = value;
unsigned short wLittleE = ((wBigE & 0xFF) << 8) | (wBigE >> 8);

per valori a 32 bit

unsigned int   iBigE = value;
unsigned int   iLittleE = ((iBigE & 0xFF) << 24)
                        | ((iBigE & 0xFF00) << 8)
                        | ((iBigE >> 8) & 0xFF00)
                        | (iBigE >> 24);

Questa non è la soluzione più efficiente a meno che il compilatore non riconosca che si tratta di una manipolazione a livello di byte e generi codice di scambio di byte. Ma non dipende da alcun trucco di layout della memoria e può essere trasformato in una macro abbastanza facilmente.


25
Sulle architetture x86 e x86_64 lo schema little endian è quello nativo.
MK aka Grisu
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.