Ho bisogno di scrivere una funzione per convertire big endian in little endian in C. Non posso usare nessuna funzione di libreria.
Ho bisogno di scrivere una funzione per convertire big endian in little endian in C. Non posso usare nessuna funzione di libreria.
Risposte:
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
((num & 0xff) >> 8) | (num << 8)
, gcc 4.8.3 genera una singola rol
istruzione. 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 bswap
istruzione.
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.
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)
#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.
#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);
}
int32_t
e int64_t
, qual è il ragionamento alla base del mascheramento di ... & 0xFFFF
e ... & 0xFFFFFFFFULL
? C'è qualcosa che sta succedendo con l'estensione del segno qui che non vedo? Inoltre, perché sta swap_int64
tornando uint64_t
? Non dovrebbe essere quello int64_t
?
swap_int64
nella tua risposta. +1 per la risposta utile, BTW!
LL
sono necessari in modo (u)swap_uint64()
molto simile a un L
non necessario in (u)swap_uint32()
. Non U
è necessario uswap_uint64()
molto come U
non è necessario inuswap_uint32()
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.
bswap
istruzione da un compilatore X86 decente con l'ottimizzazione abilitata. Questa versione con un parametro per la dimensione non poteva farlo.
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))
UINT
nel loro nome.
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 bswap
dell'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;
}
}
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;
}
int i, size_t sizeofInt
e non dello stesso tipo per entrambi.
ecco un modo utilizzando l'istruzione SSSE3 pshufb usando il suo intrinseco Intel, supponendo che tu abbia un multiplo di 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;
}
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];
char
, no byte
.
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;
}
}
source
è allineato secondo necessità, ma se tale presupposto non è valido, il codice è UB.
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.
t know if it
veloce quanto i suggerimenti ma funziona: github.com/heatblazer/helpers/blob/master/utils.h
CHAR_BIT
invece di 8
è curioso in quanto 0xFF00FF00FF00FF00ULL
dipende da CHAR_BIT == 8
. Nota che LL
non è necessario nella costante.
CHAR_BIT
per 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.
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);
}
((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.
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.