c endianness

Convertir Little Endian a Big Endian



endianness (11)

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 está almacenado en little-endian, aquí están las representaciones binarias y hexadecimales del número:

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

En el 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 malo 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 OP es incorrecto por las siguientes razones:

  • Los intercambios se realizan en un límite de nibble (4 bits), en lugar de un límite de byte (8 bits).
  • Las operaciones de desplazamiento a la izquierda << de los cuatro intercambios finales son incorrectas, deberían ser operaciones de desplazamiento a la derecha y sus valores de desplazamiento también deberían corregirse.
  • El uso del almacenamiento intermedio no es necesario y, por lo tanto, el código puede reescribirse para que sea más conciso / reconocible. Al hacerlo, algunos compiladores podrán optimizar mejor el código al reconocer el patrón que se utiliza 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 compilador optimice su código cuando sea posible. Debería evitar estructuras de datos innecesarias, como matrices, para algoritmos simples como este, ya que esto generalmente provocará diferentes conductas de instrucción, como el acceso a la memoria RAM en lugar de utilizar registros de CPU.


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


El código de muestra de OP es incorrecto.

La conversión de Endian funciona a nivel de bit y de 8 bits. La mayoría de los problemas endianos se refieren al nivel de bytes. El código OP está realizando un cambio de endian en el nivel de nibble de 4 bits. Recomienda 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 en el compilador.

[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)."

Aparece en este caso, el endian del número de 32 bits es desconocido y el resultado debe almacenarse en la memoria en el orden de little 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);

[Edición 2016] Simplificación

... El tipo de resultado es el del operando de la izquierda promovido ... Operadores de cambio a nivel de 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 que sin ella.

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


Lo siento, mi respuesta es demasiado tarde, pero parece que nadie mencionó las funciones integradas para revertir el orden de 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 que puedes encontrar en Wikipedia. Pero eso significa que nuestros procesadores convierten millones de veces entre grandes y pequeños mientras navegamos por Internet.

Es por eso que la mayoría de las arquitecturas tienen instrucciones de un procesador dedicado para facilitar esta tarea. Para arquitecturas x86 hay instrucciones BSWAP , y para ARMs hay REV . Esta es la forma más eficiente de revertir el orden de bytes .

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

Aquí hay un ejemplo:

#include <stdio.h> #include <inttypes.h> 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 desmontaje (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)

Sólo hay una instrucción BSWAP hecho.

Por lo tanto, si nos importa el rendimiento , deberíamos usar esas funciones integradas en lugar de cualquier otro método de inversión de bytes. Sólo mis 2 centavos.


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


Puedes usar las funciones lib. Se reducen al ensamblaje, pero si está abierto a implementaciones alternativas en C, aquí están (asumiendo 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);


Supongo que estás en Linux

Incluya "byteswap.h" y use int32_t bswap_32(int32_t argument);

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


Un programa C simple para convertir de pequeño a grande.

#include <stdio.h> 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; }


Una forma 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 con la 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, puse la primera uint8 recibida en .asChars [1], la segunda en .asChars [0] y luego accedí a ella como la parte .asInt de la unión en el resto de mi programa. Si tiene un valor de treinta y dos bits para almacenar, puede tener la matriz cuatro largos.


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


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

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

por lo tanto, en caso de que desee intercambiar 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 ;