Necesito escribir una función para convertir big endian a little endian en C. No puedo usar ninguna función de biblioteca.
Necesito escribir una función para convertir big endian a little endian en C. No puedo usar ninguna función de biblioteca.
Respuestas:
Suponiendo que lo que necesita es un simple intercambio de bytes, intente algo como
Conversión de 16 bits sin signo:
swapped = (num>>8) | (num<<8);
Conversión de 32 bits sin signo:
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
Esto intercambia las órdenes de bytes de las posiciones 1234 a 4321. Si su entrada fue 0xdeadbeef
, un intercambio endian de 32 bits podría tener una salida de 0xefbeadde
.
El código anterior debe limpiarse con macros o al menos constantes en lugar de números mágicos, pero es de esperar que ayude como está
EDITAR: como señaló otra respuesta, existen alternativas específicas de plataforma, sistema operativo y conjunto de instrucciones que pueden ser MUCHO más rápidas que las anteriores. En el kernel de Linux hay macros (cpu_to_be32 por ejemplo) que manejan el endianness bastante bien. Pero estas alternativas son específicas de sus entornos. En la práctica, la endianidad se aborda mejor utilizando una combinación de enfoques disponibles.
((num & 0xff) >> 8) | (num << 8)
, gcc 4.8.3 genera una única rol
instrucción. Y si la conversión de 32 bits se escribe como ((num & 0xff000000) >> 24) | ((num & 0x00ff0000) >> 8) | ((num & 0x0000ff00) << 8) | (num << 24)
, el mismo compilador genera una sola bswap
instrucción.
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 campos de bits como este: donde este es un campo de bits con 8 campos de 1 bit cada uno. Pero no estoy seguro de si eso es tan rápido como las otras sugerencias. Para enteros, utilice union { int i; byte_t[sizeof(int)]; }
para invertir byte por byte en el entero.
Incluyendo:
#include <byteswap.h>
puede obtener una versión optimizada de las funciones de intercambio de bytes dependientes de la máquina. Entonces, puede usar fácilmente las siguientes funciones:
__bswap_32 (uint32_t input)
o
__bswap_16 (uint16_t input)
#include <byteswap.h>
, consulte el comentario en el archivo .h. Esta publicación contiene información útil, así que voté a pesar de que el autor ignoró el requisito de OP de no usar una función 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);
}
Actualización : intercambio de bytes de 64 bits agregado
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
y int64_t
, ¿cuál es el razonamiento detrás del enmascaramiento de ... & 0xFFFF
y ... & 0xFFFFFFFFULL
? ¿Está sucediendo algo con la extensión de señal aquí que no veo? Además, ¿por qué swap_int64
regresa uint64_t
? ¿No debería ser así int64_t
?
swap_int64
en su respuesta. +1 para la respuesta útil, ¡por cierto!
LL
son innecesarias en (u)swap_uint64()
gran parte como un L
no es necesario en (u)swap_uint32()
. El U
no es necesario enuswap_uint64()
mucho como el U
no es necesario enuswap_uint32()
Aquí hay una versión bastante genérica; No lo he compilado, por lo que probablemente haya errores tipográficos, pero debería hacerse una 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: Esto no estáoptimizado para velocidad o espacio. Está destinado a ser claro (fácil de depurar) y portátil.
Actualización 2018-04-04 Se agregó el assert () para atrapar el caso no válido de n == 0, como lo detectó el comentarista @chux.
bswap
instrucción por un compilador X86 decente con optimización habilitada. Esta versión con un parámetro para el tamaño no pudo hacer eso.
Si necesita macros (por ejemplo, sistema integrado):
#define SWAP_UINT16(x) (((x) >> 8) | ((x) << 8))
#define SWAP_UINT32(x) (((x) >> 24) | (((x) & 0x00FF0000) >> 8) | (((x) & 0x0000FF00) << 8) | ((x) << 24))
UINT
en su nombre.
Editar: estas son funciones de biblioteca. Seguirlos es la forma manual de hacerlo.
Estoy absolutamente sorprendido por la cantidad de personas que desconocen __byteswap_ushort, __byteswap_ulong y __byteswap_uint64 . Seguro que son específicos de Visual C ++, pero se compilan en un código delicioso en arquitecturas x86 / IA-64. :)
Aquí hay un uso explícito de la bswap
instrucción, extraído de esta página . Tenga en cuenta que la forma intrínseca anterior siempre será más rápida que esta , solo la agregué para dar una respuesta sin una rutina de biblioteca.
uint32 cq_ntohl(uint32 a) {
__asm{
mov eax, a;
bswap eax;
}
}
Como una broma:
#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
y no del mismo tipo para ambos.
aquí hay una manera de usar la instrucción SSSE3 pshufb usando su intrínseco Intel, asumiendo que tiene un múltiplo de 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;
}
¿Funcionará o será más rápido?
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
.
Aquí hay una función que he estado usando, probada y funciona con cualquier tipo de datos básico:
// 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
está alineado según sea necesario; sin embargo, si esa suposición no se cumple, el código es UB.
EDITAR: Esta función solo intercambia el endianness de las palabras alineadas de 16 bits. Una función a menudo necesaria para codificaciones UTF-16 / UCS-2. EDITAR FIN.
Si desea cambiar la endiabilidad de un bloque de memoria, puede usar mi enfoque increíblemente rápido. Su matriz de memoria debe tener un tamaño múltiplo de 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);
}
Este tipo de función es útil para cambiar la endiabilidad de los archivos Unicode UCS-2 / UTF-16.
t know if it
tan rápido como las sugerencias pero funciona: github.com/heatblazer/helpers/blob/master/utils.h
CHAR_BIT
en lugar de 8
es curioso ya 0xFF00FF00FF00FF00ULL
que depende de CHAR_BIT == 8
. Tenga en cuenta que LL
no es necesario en la constante.
CHAR_BIT
para aumentar la exposición de esa macro. En cuanto al LL, es más una anotación que cualquier otra cosa. También es un hábito que adquirí hace mucho tiempo con los compiladores con errores (pre estándar) que no harían lo correcto.
Este fragmento de código puede convertir un pequeño número Endian de 32 bits en un número 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);
}
Si está ejecutando en un procesador x86 o x86_64, el big endian es nativo. entonces
para valores de 16 bits
unsigned short wBigE = value;
unsigned short wLittleE = ((wBigE & 0xFF) << 8) | (wBigE >> 8);
para valores de 32 bits
unsigned int iBigE = value;
unsigned int iLittleE = ((iBigE & 0xFF) << 24)
| ((iBigE & 0xFF00) << 8)
| ((iBigE >> 8) & 0xFF00)
| (iBigE >> 24);
Esta no es la solución más eficiente a menos que el compilador reconozca que se trata de una manipulación a nivel de bytes y genere un código de intercambio de bytes. Pero no depende de ningún truco de diseño de memoria y se puede convertir en una macro con bastante facilidad.