convertir big endian a little endian en C [sin usar la función proporcionada] [cerrado]

92

Necesito escribir una función para convertir big endian a little endian en C. No puedo usar ninguna función de biblioteca.

Alex Xander
fuente
5
un valor de 16 bits? Valor de 32 bits? ¿flotador? ¿una matriz?
John Knoeller
19
¿Quizás es hora de elegir una respuesta?
Aniket Inge
7
Votación para reabrir. Igual que stackoverflow.com/questions/105252/… para C ++. Podríamos editar para que quede más claro.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

Respuestas:

169

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.

Sam Post
fuente
5
+1 para mencionar métodos específicos de plataforma / hardware. Los programas siempre se ejecutan en algún hardware y las funciones de hardware son siempre las más rápidas.
eonil
21
si la conversión de 16 bits se realiza como ((num & 0xff) >> 8) | (num << 8), gcc 4.8.3 genera una única rolinstrucció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 bswapinstrucción.
user666412
No sé qué tan eficiente es esto, pero cambié el orden de bytes 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.
Ilian Zapryanov
Creo que la expresión debe ser: (num >> 8) | (num << 8) para invertir el orden de los bytes y NOT: ((num & 0xff) >> 8) | (num << 8), El ejemplo incorrecto obtiene cero en el byte bajo.
jscom
@IlianZapryanov Quizás +1 para mayor claridad, pero usar campos de bits en C como esa es probablemente la forma menos eficiente de hacerlo.
sherrellbc
104

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)
Amir Mgh
fuente
3
Gracias por su respuesta, pero no puedo usar ninguna función de la biblioteca
Mark Ransom
4
Debería leerse #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.
Eli Rosencruft
30
De hecho, las funciones __bswap_32 / __ bswap_16 son macros y no funciones de biblioteca, otra razón para votar a favor.
Eli Rosencruft
7
Tengo entendido que no se garantiza que este encabezado exista para todos los sistemas operativos en todas las arquitecturas. Todavía tengo que encontrar una forma portátil de lidiar con los problemas de endian.
Edward Falk
2
no existe en Windows, al menos no cuando se realiza una compilación cruzada desde linux con mingw de 32 o 64 bits
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);
}

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);
}
chmike
fuente
Para las variantes de int32_ty int64_t, ¿cuál es el razonamiento detrás del enmascaramiento de ... & 0xFFFFy ... & 0xFFFFFFFFULL? ¿Está sucediendo algo con la extensión de señal aquí que no veo? Además, ¿por qué swap_int64regresa uint64_t? ¿No debería ser así int64_t?
bgoodr
1
El swap_int64 que devuelve un uint64 es de hecho un error. El enmascaramiento con valores int con signo es de hecho para eliminar el signo. Si se desplaza a la derecha, se inyecta el bit de signo a la izquierda. Podríamos evitar esto simplemente llamando a la operación de intercambio de int sin firmar.
chmike
Gracias. Es posible que desee cambiar el tipo de valor devuelto swap_int64en su respuesta. +1 para la respuesta útil, ¡por cierto!
bgoodr
¿El bit a bit y el valor endian dependen?
MarcusJ
1
El LLson innecesarias en (u)swap_uint64()gran parte como un Lno es necesario en (u)swap_uint32(). El Uno es necesario enuswap_uint64() mucho como el Uno es necesario enuswap_uint32()
chux - Reincorporar a Monica
13

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.

Michael J
fuente
1
puede utilizar xorSwap para un mejor rendimiento. Prefiera esta versión genérica sobre todas las de tamaño específico ...
Lo probé, resulta que es más rápido que xorSwap ... en x86. stackoverflow.com/questions/3128095/…
1
@nus: una de las ventajas de un código muy simple es que el optimizador del compilador a veces puede hacerlo muy rápido.
Michael J
@MichaelJ OTOH, la versión de 32 bits anterior en la respuesta de chmike se compila en una sola bswapinstrucció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.
Alnitak
@Alnitak - Como dije, no hice ningún esfuerzo para optimizar mi código. Cuando el usuario nus descubrió que el código se ejecutaba muy rápido (en un caso), acabo de mencionar la idea general de que el código simple a menudo puede ser altamente optimizado por un compilador. Mi código funciona para una amplia variedad de casos y es bastante fácil de entender y, por lo tanto, fácil de depurar. Eso cumplió mis objetivos.
Michael J
9

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))
kol
fuente
Estas macros están bien, pero ((x) >> 24) fallará cuando un entero con signo esté entre 0x80000000 y 0xffffffff. Es una buena idea usar AND bit a bit aquí. Nota: ((x) << 24) es perfectamente seguro. (x) >> 8) también fallará si los 16 bits altos son distintos de cero (o se proporciona un valor de 16 bits con signo).
2
@ PacMan: estas macros están diseñadas para intercambiar enteros sin signo únicamente. Por eso está el UINTen su nombre.
kol
Sí, es cierto, perdón por el ruido. ¿No sería mejor insertar un encasillado?
5

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 bswapinstrucció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; 
    }
}
Sam Harwell
fuente
21
Para una pregunta de C, ¿está sugiriendo algo específico de Visual C ++?
Alok Singhal
3
@Alok: Visual C ++ es un producto de Microsoft. Funciona bien para compilar código C. :)
Sam Harwell
20
¿Por qué le sorprende que muchas personas no estén al tanto de las implementaciones específicas de Microsoft de byteswapping?
dreamlax
36
Genial, esa es una buena información para cualquiera que desarrolle un producto de código cerrado que no necesita ser portátil o compatible con los estándares.
Sam Post
6
@Alok, OP no mencionó el compilador | OS. Una persona puede dar respuestas de acuerdo con su experiencia con un conjunto particular de herramientas.
Aniket Inge
5

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;
}
Dreamlax
fuente
7
JAJAJAJAJA. Jajaja. Decir ah. ¿Decir ah? (¿Qué broma?)
3
¿Sacaste esto de algún repositorio de fuentes de Windows? :)
hochl
¡Nodejs usa esta técnica! github.com/nodejs/node/blob/…
Justin Moser
Curioso de usar int i, size_t sizeofInty no del mismo tipo para ambos.
chux - Reincorporar a Monica
5

aquí hay una manera de usar la instrucción SSSE3 pshufb usando su intrínseco Intel, asumiendo que tiene un múltiplo de 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
fuente
3

¿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];
Pablo
fuente
2
Creo que te refieres a que charno byte.
dreamlax
Con esta estrategia, la solución con más votos en comparación con la suya es equivalente y la más eficiente y portátil. Sin embargo, la solución que propongo (la segunda más votada) necesita menos operaciones y debería ser más eficiente.
chmike
1

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

}
ticketman
fuente
2
El código se basa en una suposición muy razonable: sourceestá alineado según sea necesario; sin embargo, si esa suposición no se cumple, el código es UB.
chux - Reincorporar a Monica
1

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.

Patrick Schlüter
fuente
Falta CHAR_BIT #define para completar el código.
Tõnu Samuel
Ok, agregué las inclusiones faltantes.
Patrick Schlüter
aquí hay un enlace a un intercambio en C ++, no lo hago t know if ittan rápido como las sugerencias pero funciona: github.com/heatblazer/helpers/blob/master/utils.h
Ilian Zapryanov
CHAR_BITen lugar de 8es curioso ya 0xFF00FF00FF00FF00ULLque depende de CHAR_BIT == 8. Tenga en cuenta que LLno es necesario en la constante.
chux - Restablecer a Monica
Tienes razón, chux. Solo escribí con CHAR_BITpara 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.
Patrick Schlüter
1

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);    
}
Kaushal Billore
fuente
Gracias @YuHao Soy nuevo aquí, no sé cómo formatear el texto.
Kaushal Billore
2
El uso ((i>>24)&0xff) | ((i>>8)&0xff00) | ((i&0xff00)<<8) | (i<<24);puede ser más rápido en algunas plataformas (por ejemplo, reciclar las constantes de máscara AND). Sin embargo, la mayoría de los compiladores harían esto, pero algunos compiladores simples no pueden optimizarlo por usted.
-7

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.

John Knoeller
fuente
25
En arquitecturas x86 y x86_64, el esquema little endian es el nativo.
MK también conocido como Grisu