Detectando endianness
c-preprocessor compile-time (13)
A pesar de las macros definidas por el compilador, no creo que haya una forma de tiempo de compilación para detectar esto, ya que determinar la endianness de una arquitectura implica analizar la manera en que almacena los datos en la memoria.
Aquí hay una función que hace precisamente eso:
bool IsLittleEndian () {
int i=1;
return (int)*((unsigned char *)&i)==1;
}
Actualmente estoy intentando crear un código fuente de C que maneje adecuadamente la E / S, independientemente de la naturaleza del sistema de destino.
He seleccionado "little endian" como mi convención de E / S, lo que significa que, para la CPU de big endian, necesito convertir datos mientras escribo o leo.
La conversión no es el problema. El problema al que me enfrento es detectar endianness, preferiblemente en tiempo de compilación (ya que la CPU no cambia el endianness en medio de la ejecución ...).
Hasta ahora, he estado usando esto:
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
...
#else
...
#endif
Está documentado como una macro predefinida de GCC, y Visual parece entenderlo también.
Sin embargo, he recibido un informe que indica que la verificación falla en algunos sistemas big_endian (PowerPC).
Entonces, estoy buscando una solución infalible, que asegure que la endianess se detecte correctamente, sea cual sea el compilador y el sistema de destino. Bueno, la mayoría de ellos al menos ...
[Editar]: la mayoría de las soluciones propuestas se basan en "pruebas de tiempo de ejecución". En ocasiones, los compiladores pueden evaluar adecuadamente estas pruebas durante la compilación y, por lo tanto, no tienen un rendimiento real en tiempo de ejecución.
Sin embargo, la bifurcación con algún tipo de << if (0) { ... } else { ... }
>> no es suficiente. En la implementación del código actual, la declaración de variables y funciones depende de la detección big_endian. Estos no se pueden cambiar con una sentencia if.
Bueno, obviamente, hay un plan de retroceso, que es reescribir el código ...
Preferiría evitar eso, pero, bueno, parece una esperanza decreciente ...
[Edición 2]: He probado "pruebas en tiempo de ejecución", modificando profundamente el código. Aunque hacen su trabajo correctamente, estas pruebas también afectan el rendimiento.
Esperaba que, dado que las pruebas tienen resultados predecibles, el compilador podría eliminar las ramificaciones erróneas. Pero desafortunadamente, no funciona todo el tiempo. MSVC es un buen compilador y tiene éxito en la eliminación de ramificaciones erróneas, pero GCC tiene resultados mixtos, según las versiones, el tipo de pruebas y el mayor impacto en 64 bits que en 32 bits.
Es extraño. Y también significa que no se puede garantizar que las pruebas en tiempo de ejecución sean manejadas por el compilador.
Edición 3 : en estos días, estoy usando una unión constante en tiempo de compilación, esperando que el compilador la resuelva con una clara señal de sí / no. Y funciona bastante bien: https://godbolt.org/g/DAafKo
Como otros lo han señalado, no hay una manera portátil de verificar el endianismo en el momento de la compilación. Sin embargo, una opción sería usar la herramienta autoconf
como parte de su script de compilación para detectar si el sistema es big-endian o little-endian, y luego usar la macro AC_C_BIGENDIAN
, que contiene esta información. En cierto sentido, esto crea un programa que detecta en tiempo de ejecución si el sistema es big-endian o little-endian, luego tiene esa información de salida del programa que luego puede ser utilizada de forma estática por el código fuente principal.
¡Espero que esto ayude!
Como se indicó anteriormente, la única forma "real" de detectar Big Endian es usar pruebas de tiempo de ejecución.
Sin embargo, a veces, una macro puede ser preferida.
Desafortunadamente, no he encontrado una sola "prueba" para detectar esta situación, sino una colección de ellos.
Por ejemplo, GCC recomienda: __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
. Sin embargo, esto solo funciona con las últimas versiones, y las versiones anteriores (y otros compiladores) le darán a esta prueba un valor falso "verdadero", ya que NULL == NULL. Entonces necesitas la versión más completa: defined(__BYTE_ORDER__)&&(__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)
OK, ahora esto funciona para el nuevo GCC, pero ¿qué pasa con otros compiladores?
Puede probar __BIG_ENDIAN__
o __BIG_ENDIAN
o _BIG_ENDIAN
que a menudo se definen en los compiladores big endian.
Esto mejorará la detección. Pero si se dirige específicamente a las plataformas PowerPC, puede agregar algunas pruebas más para mejorar aún más la detección. Pruebe _ARCH_PPC
o __PPC__
o __PPC
o PPC
o __powerpc__
o __powerpc
o incluso powerpc
. Vincule todas estas definiciones juntas, y tendrá una oportunidad bastante justa para detectar sistemas big endian, y en particular powerpc, sea cual sea el compilador y su versión.
Entonces, para resumir, no existe tal cosa como una "macros predefinidas estándar" que garantiza detectar la CPU de big-endian en todas las plataformas y compiladores, pero hay muchas de esas macros predefinidas que, en conjunto, dan una alta probabilidad de detectar correctamente big endian en la mayoría de las circunstancias.
En el momento de la compilación en C, no puede hacer mucho más que confiar en el preprocesador #define
s, y no hay soluciones estándar porque el estándar C no se preocupa por la endianidad.
Aún así, podría agregar una aserción que se haga en tiempo de ejecución al inicio del programa para asegurarse de que la suposición hecha al compilar fue verdadera:
inline int IsBigEndian()
{
int i=1;
return ! *((char *)&i);
}
/* ... */
#ifdef COMPILED_FOR_BIG_ENDIAN
assert(IsBigEndian());
#elif COMPILED_FOR_LITTLE_ENDIAN
assert(!IsBigEndian());
#else
#error "No endianness macro defined"
#endif
(donde COMPILED_FOR_BIG_ENDIAN
y COMPILED_FOR_LITTLE_ENDIAN
son macros #define
d previamente de acuerdo con las comprobaciones de endianness de su preprocesador)
En lugar de buscar una verificación en tiempo de compilación, ¿por qué no solo usa el orden big-endian (que muchos consideran el "pedido de la red" ) y usa las htons/htonl/ntohs/ntohl provistas por la mayoría de los sistemas UNIX y Windows? Ya están definidos para hacer el trabajo que estás tratando de hacer. ¿Por qué reinventar la rueda?
Esto viene de p. 45 de los punteros en C :
#include <stdio.h>
#define BIG_ENDIAN 0
#define LITTLE_ENDIAN 1
int endian()
{
short int word = 0x0001;
char *byte = (char *) &word;
return (byte[0] ? LITTLE_ENDIAN : BIG_ENDIAN);
}
int main(int argc, char* argv[])
{
int value;
value = endian();
if (value == 1)
printf("The machine is Little Endian/n");
else
printf("The machine is Big Endian/n");
return 0;
}
Intenta algo como:
if(*(char *)(int[]){1}) {
/* little endian code */
} else {
/* big endian code */
}
y ver si su compilador lo resuelve en tiempo de compilación. Si no es así, es posible que tenga mejor suerte haciendo lo mismo con un sindicato. En realidad, me gusta definir macros usando uniones que se evalúan a 0,1 o 1,0 (respectivamente) para poder hacer cosas como acceder a buf[HI]
y buf[LO]
.
La función ntohl
de Socket se puede utilizar para este propósito. Source
// Soner
#include <stdio.h>
#include <arpa/inet.h>
int main() {
if (ntohl(0x12345678) == 0x12345678) {
printf("big-endian/n");
} else if (ntohl(0x12345678) == 0x78563412) {
printf("little-endian/n");
} else {
printf("(stupid)-middle-endian/n");
}
return 0;
}
Me tomé la libertad de reformatear el texto citado
A partir de 2017-07-18, uso
union { unsigned u; unsigned char c[4]; }
union { unsigned u; unsigned char c[4]; }
Si sizeof (unsigned) != 4
su prueba puede fallar.
Puede ser mejor usar
union { unsigned u; unsigned char c[sizeof (unsigned)]; }
No es posible detectar el endianness de forma portátil en C con directivas de preprocesador.
No se puede detectar en tiempo de compilación para ser portátil en todos los compiladores. Tal vez pueda cambiar el código para hacerlo en tiempo de ejecución, esto es posible.
Sé que llego tarde a esta fiesta, pero aquí está mi opinión.
int is_big_endian() {
return 1 & *(uint16_t*)"01";
}
Esto se basa en el hecho de que ''0''
es 48 en decimal y ''1''
49, por lo que ''1''
tiene el bit LSB establecido, mientras que ''0''
no. Podría hacerlos ''/x00''
y ''/x01''
pero creo que mi versión lo hace más legible.
#define BIG_ENDIAN ((1 >> 1 == 0) ? 0 : 1)