c++ c endianness

¿Cómo escribir endian agnostic C/C++ code?



endianness (7)

Hice algunas búsquedas en Google y no pude encontrar ningún buen artículo sobre esta cuestión. ¿Qué debo tener cuidado cuando implemente una aplicación que quiero que sea agnóstica para endian?


¿Qué debo tener cuidado cuando implemente una aplicación que quiero que sea agnóstica para endian?

Primero debes reconocer cuándo endian se convierte en un problema. Y sobre todo se convierte en un problema cuando tiene que leer o escribir datos desde un lugar externo, ya sea leyendo datos de un archivo o haciendo una comunicación de red entre computadoras.

En tales casos, la endiosidad es importante para enteros mayores que un byte, ya que los enteros se representan de forma diferente en la memoria por diferentes plataformas. Esto significa que cada vez que necesite leer o escribir datos externos, necesita hacer algo más que simplemente descargar la memoria de su programa, o leer datos directamente en sus propias variables.

por ejemplo, si tiene este fragmento de código:

unsigned int var = ...; write(fd, &var, sizeof var);

Está escribiendo directamente el contenido de la memoria de var , lo que significa que los datos se presentan en todos los datos que vayan tal como se representan en la memoria de su propia computadora.

Si escribe estos datos en un archivo, el contenido del archivo será diferente ya sea que ejecute el programa en un big endian o en una pequeña máquina de endian. Entonces, ese código no es endógeno y te gustaría evitar hacer cosas como esta.

En lugar de enfocarse en el formato de datos. Al leer / escribir datos, siempre decida primero el formato de datos y luego escriba el código para manejarlo. Esto ya podría haber sido decidido por usted si necesita leer algún formato de archivo bien definido existente o implementar un protocolo de red existente.

Una vez que conoce el formato de los datos, en lugar de, por ejemplo, eliminar una variable int directamente, su código hace esto:

uint32_t i = ...; uint8_t buf[4]; buf[0] = (i&0xff000000) >> 24; buf[1] = (i&0x00ff0000) >> 16; buf[2] = (i&0x0000ff00) >> 8; buf[3] = (i&0x000000ff); write(fd, buf, sizeof buf);

Ahora hemos elegido el byte más significativo y lo hemos colocado como el primer byte en un buffer, y el byte menos significativo colocado al final del buffer. Ese entero se representa en formato big endian en buf , independientemente del endian del host, por lo que este código es agnóstico endian.

El consumidor de estos datos debe saber que los datos están representados en un formato Big Endian. E independientemente del host en el que se ejecute el programa, este código leerá los datos correctamente:

uint32_t i; uint8_t buf[4]; read(fd, buf, sizeof buf); i = (uint32_t)buf[0] << 24; i |= (uint32_t)buf[1] << 16; i |= (uint32_t)buf[2] << 8; i |= (uint32_t)buf[3];

Por el contrario, si se sabe que los datos que necesita leer se encuentran en formato little endian, el código agnóstico endianess simplemente lo haría

uint32_t i ; uint8_t buf[4]; read(fd, buf, sizeof buf); i = (uint32_t)buf[3] << 24; i |= (uint32_t)buf[2] << 16; i |= (uint32_t)buf[1] << 8; i |= (uint32_t)buf[0];

Puedes hacer algunas bonitas funciones en línea o macros para envolver y desenvolver todos los tipos enteros de 2,4,8 bytes que necesites, y si los usas y te importa el formato de datos y no el endian del procesador en el que trabajas, tu código no depender de la endianess en la que se está ejecutando.

Esto es más código que muchas otras soluciones, todavía tengo que escribir un programa donde este trabajo adicional haya tenido un impacto significativo en el rendimiento, incluso cuando se barajan 1Gbps + de datos.

También evita el acceso a la memoria mal alineado que puede obtener fácilmente con un enfoque de, por ejemplo,

uint32_t i; uint8_t buf[4]; read(fd, buf, sizeof buf); i = ntohl(*(uint32_t)buf));

que también puede incurrir en un golpe de rendimiento (insignificante en algunos, muchos muchos órdenes de magnitud en otros) en el mejor de los casos, y un colapso en las plataformas que no pueden hacer acceso desalineado a los enteros.


Dentro de tu código puedes prácticamente ignorarlo, todo se cancela.

Cuando lee / escribe datos en un disco o en la red, use htons


El único momento en que debe preocuparse por la endianancia es cuando transfiere datos binarios sensibles a endian (es decir, no texto) entre sistemas que pueden no tener el mismo endianness. La solución normal es usar " orden de bytes de red " (AKA big-endian) para transferir datos, y luego cambiar los bytes si es necesario en el otro extremo.

Para convertir del host a la orden de bytes de la red, use htons(3) y htonl(3) . Para convertir de nuevo, use ntohl(3) y ntohs(3) . Consulte la página del manual para ver todo lo que necesita saber. Para datos de 64 bits, esta pregunta y respuesta serán útiles.


Este es claramente un tema bastante controvertido.

El enfoque general es diseñar su aplicación de modo que solo le interese el byteorder en una pequeña porción: las secciones de entrada y salida del código.

En cualquier otro lugar, debe usar el orden de bytes nativo.

Tenga en cuenta que aunque las máquinas MOST hacen esto de la misma manera, no se garantiza que los datos flotantes y enteros se almacenen de la misma manera, así que para estar completamente seguros de que las cosas funcionan bien, debe saber no solo el tamaño, sino también entero o punto flotante.

La otra alternativa es solo consumir y producir datos en formato de texto. Esto es probablemente casi tan fácil de implementar, y a menos que tenga una tasa de datos realmente alta dentro y fuera de la aplicación con muy poco procesamiento, es probable que haya muy poca diferencia en el rendimiento. Y con la ventaja (para algunos) de que puede leer los datos de entrada y salida en un editor de texto, en lugar de tratar de decodificar el valor de los bytes 51213498-51213501 en la salida en realidad, cuando tiene algo mal en el código.


Este podría ser un buen artículo para que usted lea: La falacia de orden de bytes

El orden de bytes de la computadora no tiene mucha importancia, excepto para los escritores de compiladores y demás, que se preocupan por la asignación de bytes de memoria mapeados para registrar piezas. Lo más probable es que no sea un escritor de compilación, por lo que el orden de bytes de la computadora no debería importarle un poco.

Observe la frase "orden de bytes de la computadora". Lo que sí importa es el orden de bytes de un flujo de datos periférico o codificado, pero -y este es el punto clave- el orden de bytes de la computadora que realiza el procesamiento es irrelevante para el procesamiento de los datos en sí. Si la secuencia de datos codifica los valores con el orden de bytes B, entonces el algoritmo para decodificar el valor en la computadora con orden de bytes C debe ser acerca de B, no sobre la relación entre B y C.


Si necesita reinterpretar entre un tipo entero de 2,4 u 8 bytes y una matriz indexada por bytes (o viceversa), entonces debe conocer la endianidad.

Esto aparece con frecuencia en la implementación de algoritmos criptográficos, aplicaciones de serialización (como protocolo de red, sistemas de archivos o bases de datos) y, por supuesto, kernels y controladores del sistema operativo.

Por lo general, se detecta mediante una macro como ENDIAN ... algo.

Por ejemplo:

uint32 x = ...; uint8* p = (uint8*) &x;

p apunta al byte alto en las máquinas BE y al byte bajo en la máquina LE.

Usando las macros puedes escribir:

uint32 x = ...; #ifdef LITTLE_ENDIAN uint8* p = (uint8*) &x + 3; #else // BIG_ENDIAN uint8* p = (uint8*) &x; #endif

para obtener siempre el byte alto, por ejemplo.

Aquí hay formas de definir la macro: C Definición de macro para determinar big endian o little endian machine? si su cadena de herramientas no los proporciona.


Varias respuestas han cubierto el archivo IO, que sin duda es la preocupación endian más común. Tocaré uno que aún no se ha mencionado: Sindicatos .

La siguiente unión es una herramienta común en la programación de SIMD / SSE, y no es compatible con los endian:

union uint128_t { _m128i dq; uint64_t dd[2]; uint32_t dw[4]; uint16_t dh[8]; uint8_t db[16]; };

Cualquier código que acceda a los formularios dd / dw / dh / db lo hará en forma específica para endian. En las CPU de 32 bits también es algo común ver uniones simples que permiten romper más fácilmente la aritmética de 64 bits en porciones de 32 bits:

union u64_parts { uint64_t dd; uint32_t dw[2]; };

Como en este caso de uso es raro (si es que lo es) que quiera iterar sobre cada elemento de la unión, prefiero escribir uniones como esta:

union u64_parts { uint64_t dd; struct { #ifdef BIG_ENDIAN uint32_t dw2, dw1; #else uint32_t dw1, dw2; #endif } };

El resultado es un intercambio de endian implícito para cualquier código que acceda directamente a dw1 / dw2. El mismo enfoque de diseño también se puede usar para el tipo de datos SIMD de 128 bits anterior, aunque termina siendo mucho más detallado.

Descargo de responsabilidad: El uso de la Unión a menudo es desaprobado debido a las definiciones de estándares sueltos con respecto al relleno de la estructura y la alineación. Considero que los sindicatos son muy útiles y los he usado extensamente, y no me he encontrado con problemas de compatibilidad cruzada en mucho tiempo (más de 15 años). El relleno / alineamiento de unión se comportará de forma esperada y consistente para cualquier compilación actual que tenga como objetivo x86, ARM o PowerPC.