Convertir Little Endian en Big Endian

Solo quiero preguntar si mi método es correcto para convertir de little endian a big endian, solo para asegurarme de que entiendo la diferencia.

Tengo un número que se almacena en little-endian, aquí están las representaciones binarias y hexagonales del número:

‭0001 0010 0011 0100 0101 0110 0111 1000‬ ‭12345678‬ 

En formato big-endian creo que los bytes deberían intercambiarse así:

 1000 0111 0110 0101 0100 0011 0010 0001 ‭87654321 

¿Es esto correcto?

Además, el código siguiente intenta hacer esto pero falla. ¿Hay algo obviamente mal o puedo optimizar algo? Si el código es incorrecto para esta conversión, ¿puede explicar por qué y mostrar un método mejor para realizar la misma conversión?

 uint32_t num = 0x12345678; uint32_t b0,b1,b2,b3,b4,b5,b6,b7; uint32_t res = 0; b0 = (num & 0xf) << 28; b1 = (num & 0xf0) << 24; b2 = (num & 0xf00) << 20; b3 = (num & 0xf000) << 16; b4 = (num & 0xf0000) << 12; b5 = (num & 0xf00000) << 8; b6 = (num & 0xf000000) << 4; b7 = (num & 0xf0000000) << 4; res = b0 + b1 + b2 + b3 + b4 + b5 + b6 + b7; printf("%d\n", res); 

El código de muestra de OP es incorrecto.

La conversión de Endian funciona en el bit y en el nivel de bytes de 8 bits. La mayoría de los problemas de Endian tienen que ver con el nivel de bytes. El código OP está haciendo un cambio endian en el nivel de cuarteto de 4 bits. Recomiende en su lugar:

 // Swap endian (big to little) or (little to big) uint32_t num = 9; uint32_t b0,b1,b2,b3; uint32_t res; b0 = (num & 0x000000ff) << 24u; b1 = (num & 0x0000ff00) << 8u; b2 = (num & 0x00ff0000) >> 8u; b3 = (num & 0xff000000) >> 24u; res = b0 | b1 | b2 | b3; printf("%" PRIX32 "\n", res); 

Si el rendimiento es realmente importante, el procesador en particular debería ser conocido. De lo contrario, déjalo al comstackdor.

[Editar] OP agregó un comentario que cambia las cosas.
“El valor numérico de 32 bits representado por la representación hexadecimal (st uv wx yz) se registrará en un campo de cuatro bytes como (st uv wx yz)”.

Parece que en este caso, el endian del número de 32 bits se desconoce y el resultado debe almacenarse en la memoria en un pequeño orden endian.

 uint32_t num = 9; uint8_t b[4]; b[0] = (uint8_t) (num >> 0u); b[1] = (uint8_t) (num >> 8u); b[2] = (uint8_t) (num >> 16u); b[3] = (uint8_t) (num >> 24u); 

[2016 Edit] Simplificación

… El tipo de resultado es el del operando izquierdo promovido … Operadores de desplazamiento bit a bit C11 §6.5.7 3

El uso de una u después de las constantes de cambio (operandos de la derecha) da como resultado lo mismo que sin él.

 b3 = (num & 0xff000000) >> 24u; b[3] = (uint8_t) (num >> 24u); // same as b3 = (num & 0xff000000) >> 24; b[3] = (uint8_t) (num >> 24); 

Creo que puedes usar la función htonl() . El orden de bytes de red es big endian.

“Cambio cada byte, ¿verdad?” -> sí, para convertir entre little y big endian, simplemente le das a los bytes el orden opuesto. Pero al principio se dan cuenta algunas cosas:

  • el tamaño de uint32_t es de uint32_t bits, que es de 4 bytes, que son 8 dígitos HEX
  • la máscara 0xf recupera los 4 bits menos significativos, para recuperar 8 bits, necesita 0xff

entonces, en caso de que quiera cambiar el orden de 4 bytes con ese tipo de máscaras, podría:

 uint32_t res = 0; b0 = (num & 0xff) << 24; ; least significant to most significant b1 = (num & 0xff00) << 8; ; 2nd least sig. to 2nd most sig. b2 = (num & 0xff0000) >> 8; ; 2nd most sig. to 2nd least sig. b3 = (num & 0xff000000) >> 24; ; most sig. to least sig. res = b0 | b1 | b2 | b3 ; 

Podrías hacer esto:

 int x = 0x12345678; x = ( x >> 24 ) | (( x << 8) & 0x00ff0000 )| ((x >> 8) & 0x0000ff00) | ( x << 24) ; printf("value = %x", x); // x will be printed as 0x78563412 

Una manera ligeramente diferente de abordar esto que a veces puede ser útil es tener una unión del valor de dieciséis o treinta y dos bits y una matriz de caracteres. Acabo de hacer esto cuando recibo mensajes en serie que vienen en orden big endian, pero estoy trabajando en un pequeño micro endian.

union MessageLengthUnion {

 uint16_t asInt; uint8_t asChars[2]; 

};

Luego, cuando recibo los mensajes, pongo el primer uint8 recibido en .asChars [1], el segundo en .asChars [0] y luego accedo a él como .ASInt parte de la unión en el rest de mi progtwig. Si tiene un valor de treinta y dos bits para almacenar, puede tener la matriz cuatro de largo.

una sugerencia más:

 unsigned int a = 0xABCDEF23; a = ((a&(0x0000FFFF)) << 16) | ((a&(0xFFFF0000)) >> 16); a = ((a&(0x00FF00FF)) << 8) | ((a&(0xFF00FF00)) >>8); printf("%0x\n",a); 

Supongo que estás en Linux

Incluye "byteswap.h" y usa int32_t bswap_32(int32_t argument);

Es una vista lógica, en la vista real, /usr/include/byteswap.h

El código de OP es incorrecto por las siguientes razones:

  • Los swaps se realizan en un límite de nibble (4 bits), en lugar de un límite de byte (8 bits).
  • Las operaciones shift-left << de los últimos cuatro swaps son incorrectas, deberían ser operaciones shift-right >> y sus valores de shift también deberían corregirse.
  • El uso del almacenamiento intermedio es innecesario, y el código puede por lo tanto reescribirse para ser más conciso / reconocible. Al hacerlo, algunos comstackdores podrán optimizar mejor el código al reconocer el patrón que se usa con frecuencia.

Considere el siguiente código, que convierte de manera eficiente un valor sin firmar:

 // Swap endian (big to little) or (little to big) uint32_t num = 0x12345678; uint32_t res = ((num & 0x000000FF) << 16) | ((num & 0x0000FF00) << 8) | ((num & 0x00FF0000) >> 8) | ((num & 0xFF000000) >> 16); printf("%0x\n", res); 

El resultado se representa aquí tanto en binario como en hexadecimal, observe cómo se han intercambiado los bytes:

 ‭0111 1000 0101 0110 0011 0100 0001 0010‬ 78563412 

Optimizando

En términos de rendimiento, deje que el comstackdor optimice su código cuando sea posible. Debería evitar estructuras de datos innecesarias, como matrices, para algoritmos simples como este, ya que normalmente provocará un comportamiento de instrucción diferente, como acceder a la memoria RAM en lugar de utilizar registros de la CPU.

Puedes usar las funciones lib. Se reducen al ensamblaje, pero si está abierto a implementaciones alternativas en C, aquí están (suponiendo que int es de 32 bits):

 void byte_swap16(unsigned short int *pVal16) { //#define method_one 1 // #define method_two 1 #define method_three 1 #ifdef method_one unsigned char *pByte; pByte = (unsigned char *) pVal16; *pVal16 = (pByte[0] << 8) | pByte[1]; #endif #ifdef method_two unsigned char *pByte0; unsigned char *pByte1; pByte0 = (unsigned char *) pVal16; pByte1 = pByte0 + 1; *pByte0 = *pByte0 ^ *pByte1; *pByte1 = *pByte0 ^ *pByte1; *pByte0 = *pByte0 ^ *pByte1; #endif #ifdef method_three unsigned char *pByte; pByte = (unsigned char *) pVal16; pByte[0] = pByte[0] ^ pByte[1]; pByte[1] = pByte[0] ^ pByte[1]; pByte[0] = pByte[0] ^ pByte[1]; #endif } void byte_swap32(unsigned int *pVal32) { #ifdef method_one unsigned char *pByte; // 0x1234 5678 --> 0x7856 3412 pByte = (unsigned char *) pVal32; *pVal32 = ( pByte[0] << 24 ) | (pByte[1] << 16) | (pByte[2] << 8) | ( pByte[3] ); #endif #if defined(method_two) || defined (method_three) unsigned char *pByte; pByte = (unsigned char *) pVal32; // move lsb to msb pByte[0] = pByte[0] ^ pByte[3]; pByte[3] = pByte[0] ^ pByte[3]; pByte[0] = pByte[0] ^ pByte[3]; // move lsb to msb pByte[1] = pByte[1] ^ pByte[2]; pByte[2] = pByte[1] ^ pByte[2]; pByte[1] = pByte[1] ^ pByte[2]; #endif } 

Y el uso se realiza así:

 unsigned short int u16Val = 0x1234; byte_swap16(&u16Val); unsigned int u32Val = 0x12345678; byte_swap32(&u32Val); 

Un progtwig Simple C para convertir de poco a grande

 #include  int main() { unsigned int little=0x1234ABCD,big=0; unsigned char tmp=0,l; printf(" Little endian little=%x\n",little); for(l=0;l < 4;l++) { tmp=0; tmp = little | tmp; big = tmp | (big << 8); little = little >> 8; } printf(" Big endian big=%x\n",big); return 0; } 

Lo siento, mi respuesta es un poco tarde, pero parece que nadie mencionó las funciones incorporadas para invertir el orden de los bytes, lo que es muy importante en términos de rendimiento .

La mayoría de los procesadores modernos son little-endian, mientras que todos los protocolos de red son big-endian. Eso es historia y más sobre eso puedes encontrar en Wikipedia. Pero eso significa que nuestros procesadores se convierten entre pequeños y grandes millones de veces mientras navegamos por Internet.

Es por eso que la mayoría de las architectures tienen instrucciones de procesador dedicadas para facilitar esta tarea. Para architectures x86 hay instrucción BSWAP , y para ARM hay REV . Esta es la forma más eficiente de invertir el orden de bytes .

Para evitar el ensamblaje en nuestro código C, podemos usar integradas en su lugar. Para GCC hay __builtin_bswap32() función y para Visual C ++ hay _byteswap_ulong() . Esa función generará solo una instrucción de procesador en la mayoría de las architectures.

Aquí hay un ejemplo:

 #include  #include  int main() { uint32_t le = 0x12345678; uint32_t be = __builtin_bswap32(le); printf("Little-endian: 0x%" PRIx32 "\n", le); printf("Big-endian: 0x%" PRIx32 "\n", be); return 0; } 

Aquí está la salida que produce:

 Little-endian: 0x12345678 Big-endian: 0x78563412 

Y aquí está el desassembly (sin optimización, es decir, -O0 ):

  uint32_t be = __builtin_bswap32(le); 0x0000000000400535 <+15>: mov -0x8(%rbp),%eax 0x0000000000400538 <+18>: bswap %eax 0x000000000040053a <+20>: mov %eax,-0x4(%rbp) 

De hecho, solo hay una instrucción BSWAP .

Entonces, si nos importa el rendimiento , deberíamos usar esas funciones incorporadas en lugar de cualquier otro método de inversión de byte. Solo mis 2 centavos.