c++ algorithm endianness

Detectando endianness programmatically en un programa de C++



algorithm (27)

¿Hay alguna manera programática de detectar si estás o no en una arquitectura big-endian o little-endian? Necesito poder escribir código que se ejecutará en un sistema Intel o PPC y usará exactamente el mismo código (es decir, sin compilación condicional).


¿Qué tal esto?

#include <cstdio> int main() { unsigned int n = 1; char *p = 0; p = (char*)&n; if (*p == 1) std::printf("Little Endian/n"); else if (*(p + sizeof(int) - 1) == 1) std::printf("Big Endian/n"); else std::printf("What the crap?/n"); return 0; }


A menos que el encabezado endian sea solo GCC, proporciona macros que puede usar.

#include "endian.h" ... if (__BYTE_ORDER == __LITTLE_ENDIAN) { ... } else if (__BYTE_ORDER == __BIG_ENDIAN) { ... } else { throw std::runtime_error("Sorry, this version does not support PDP Endian!"); ...


A menos que esté utilizando un marco que haya sido portado a procesadores PPC e Intel, tendrá que hacer compilaciones condicionales, ya que las plataformas PPC e Intel tienen arquitecturas de hardware, tuberías, buses, etc. completamente diferentes. Esto hace que el código ensamblador sea completamente diferente los dos.

En cuanto a encontrar endianness, haz lo siguiente:

short temp = 0x1234; char* tempChar = (char*)&temp;

Obtendrá tempChar de 0x12 o 0x34, de lo que sabrá la endianidad.


Aquí hay otra versión C. Define una macro llamada wicked_cast() para el wicked_cast() palabras en línea a través de los literales de unión C99 y el operador __typeof__ no estándar.

#include <limits.h> #if UCHAR_MAX == UINT_MAX #error endianness irrelevant as sizeof(int) == 1 #endif #define wicked_cast(TYPE, VALUE) / (((union { __typeof__(VALUE) src; TYPE dest; }){ .src = VALUE }).dest) _Bool is_little_endian(void) { return wicked_cast(unsigned char, 1u); }

Si los enteros son valores de un solo byte, la endianidad no tiene sentido y se generará un error en tiempo de compilación.


Como se indicó anteriormente, use trucos sindicales.

Sin embargo, hay algunos problemas con los recomendados anteriormente, sobre todo que el acceso a memoria no alineado es notoriamente lento para la mayoría de las arquitecturas, y algunos compiladores no reconocerán tales predicados constantes, a menos que estén alineados.

Como la mera prueba endian es aburrida, aquí va la función (plantilla) que volteará la entrada / salida del entero arbitrario de acuerdo con su especificación, independientemente de la arquitectura del host.

#include <stdint.h> #define BIG_ENDIAN 1 #define LITTLE_ENDIAN 0 template <typename T> T endian(T w, uint32_t endian) { // this gets optimized out into if (endian == host_endian) return w; union { uint64_t quad; uint32_t islittle; } t; t.quad = 1; if (t.islittle ^ endian) return w; T r = 0; // decent compilers will unroll this (gcc) // or even convert straight into single bswap (clang) for (int i = 0; i < sizeof(r); i++) { r <<= 8; r |= w & 0xff; w >>= 8; } return r; };

Uso:

Para convertir de endian determinado a host, use:

host = endian(source, endian_of_source)

Para convertir del host endian al endian dado, use:

output = endian(hostsource, endian_you_want_to_output)

El código resultante es tan rápido como escribir ensamblaje de mano en clang, en gcc es un poco más lento (desenrollado y, <<, >>, | para cada byte) pero aún decente.


Como señaló Coriiander, la mayoría (si no todos) de esos códigos aquí se optimizarán en el tiempo de compilación, por lo que los binarios generados no verificarán la "endianidad" en tiempo de ejecución.

Se ha observado que un ejecutable dado no debe ejecutarse en dos órdenes de bytes diferentes, pero no tengo idea de si ese es siempre el caso, y me parece un truco comprobar el tiempo de compilación. Así que codifiqué esta función:

#include <stdint.h> int* _BE = 0; int is_big_endian() { if (_BE == 0) { uint16_t* teste = (uint16_t*)malloc(4); *teste = (*teste & 0x01FE) | 0x0100; uint8_t teste2 = ((uint8_t*) teste)[0]; free(teste); _BE = (int*)malloc(sizeof(int)); *_BE = (0x01 == teste2); } return *_BE; }

MinGW no pudo optimizar este código, aunque optimiza los otros códigos aquí. Creo que es porque dejo el valor "aleatorio" alocated en la memoria de bytes más pequeños (como mínimo 7 de sus bits), por lo que el compilador no puede saber cuál es ese valor aleatorio y no optimiza la función de distancia.

También he codificado la función para que la verificación solo se realice una vez y el valor de retorno se almacene para las próximas pruebas.


Consulte Endianness : ilustración del código de nivel C.

// assuming target architecture is 32-bit = 4-Bytes enum ENDIANESS{ LITTLEENDIAN , BIGENDIAN , UNHANDLE }; ENDIANESS CheckArchEndianalityV1( void ) { int Endian = 0x00000001; // assuming target architecture is 32-bit // as Endian = 0x00000001 so MSB (Most Significant Byte) = 0x00 and LSB (Least Significant Byte) = 0x01 // casting down to a single byte value LSB discarding higher bytes return (*(char *) &Endian == 0x01) ? LITTLEENDIAN : BIGENDIAN; }


Declare una variable int:

int variable = 0xFF;

Ahora use los caracteres char * en varias partes y verifique qué hay en esas partes.

char* startPart = reinterpret_cast<char*>( &variable ); char* endPart = reinterpret_cast<char*>( &variable ) + sizeof( int ) - 1;

Dependiendo de cuál apunta a 0xFF byte ahora puede detectar endianness. Esto requiere sizeof (int)> sizeof (char), pero definitivamente es cierto para las plataformas discutidas.


Ehm ... Me sorprende que nadie se haya dado cuenta de que el compilador simplemente optimizará la prueba, y pondrá un resultado fijo como valor de retorno. Esto representa todos los ejemplos de código anteriores, efectivamente inútiles. ¡Lo único que se devolverá es la endianidad en tiempo de compilación! Y sí, probé todos los ejemplos anteriores. Aquí hay un ejemplo con MSVC 9.0 (Visual Studio 2008).

Código Pure C

int32 DNA_GetEndianness(void) { union { uint8 c[4]; uint32 i; } u; u.i = 0x01020304; if (0x04 == u.c[0]) return DNA_ENDIAN_LITTLE; else if (0x01 == u.c[0]) return DNA_ENDIAN_BIG; else return DNA_ENDIAN_UNKNOWN; }

Desmontaje

PUBLIC _DNA_GetEndianness ; Function compile flags: /Ogtpy ; File c:/development/dna/source/libraries/dna/endian.c ; COMDAT _DNA_GetEndianness _TEXT SEGMENT _DNA_GetEndianness PROC ; COMDAT ; 11 : union ; 12 : { ; 13 : uint8 c[4]; ; 14 : uint32 i; ; 15 : } u; ; 16 : ; 17 : u.i = 1; ; 18 : ; 19 : if (1 == u.c[0]) ; 20 : return DNA_ENDIAN_LITTLE; mov eax, 1 ; 21 : else if (1 == u.c[3]) ; 22 : return DNA_ENDIAN_BIG; ; 23 : else ; 24 : return DNA_ENDIAN_UNKNOWN; ; 25 : } ret _DNA_GetEndianness ENDP END

Quizás es posible desactivar CUALQUIER optimización en tiempo de compilación solo para esta función, pero no sé. De lo contrario, es posible codificarlo en el ensamblaje, aunque eso no es portátil. E incluso entonces, incluso eso podría optimizarse. Me hace pensar que necesito un ensamblador realmente malo, implementar el mismo código para todas las CPU / conjuntos de instrucciones existentes, y bueno ... no importa.

Además, alguien aquí dijo que la endianidad no cambia durante el tiempo de ejecución. INCORRECTO. Hay máquinas bi-endian por ahí. Su endianidad puede variar durante la ejecución. TAMBIÉN, no solo hay Little Endian y Big Endian, sino también otras endianzas (qué palabra).

Odio y amo la codificación al mismo tiempo ...


Estaba repasando el libro de texto: Sistema informático: la perspectiva de un programador , y hay un problema para determinar qué endian es esto por el programa C.

Usé la función del puntero para hacer eso de la siguiente manera:

#include <stdio.h> int main(void){ int i=1; unsigned char* ii = &i; printf("This computer is %s endian./n", ((ii[0]==1) ? "little" : "big")); return 0; }

Como int ocupa 4 bytes, y char ocupa solo 1 byte. Podríamos usar un puntero char para apuntar al int con el valor 1. Por lo tanto, si la computadora es poco endian, el carácter al que apunta el puntero char es con el valor 1, de lo contrario, su valor debería ser 0.


Esto normalmente se realiza en tiempo de compilación (especialmente por razones de rendimiento) utilizando los archivos de encabezado disponibles del compilador o crea los tuyos propios. En Linux tiene el archivo de encabezado "/usr/include/endian.h"


Haría algo como esto:

bool isBigEndian() { static unsigned long x(1); static bool result(reinterpret_cast<unsigned char*>(&x)[0] == 0); return result; }

En esta línea, obtendría una función de tiempo eficiente que solo hace el cálculo una vez.


La forma en C ++ ha sido usar boost , donde los controles y moldes del preprocesador se dividen en compartimentos en bibliotecas muy bien probadas.

La Biblioteca Predef (boost / predef.h) reconoce cuatro tipos diferentes de endianness .

La Biblioteca Endian se planificó para enviarse al estándar C ++ y admite una amplia variedad de operaciones en datos sensibles a endian.

Como se indica en las respuestas anteriores, Endianness será una parte de c ++ 20.


La forma en que los compiladores de C (al menos todos los que conozco) trabajan en la endianidad debe decidirse en tiempo de compilación. Incluso para procesadores biendianos (como ARM och MIPS), debe elegir endianness en tiempo de compilación. Además, la endianidad se define en todos los formatos de archivo comunes para ejecutables (como ELF). Aunque es posible crear un blob binario de código biandian (¿para algún exploit de servidor ARM tal vez?) Probablemente deba hacerse en ensamblaje.


Me sorprendió que nadie haya mencionado las macros que el pre-procesador define por defecto. Si bien estos variarán según su plataforma; son mucho más limpios que tener que escribir tu propio endian-check.

Por ejemplo; si miramos las macros integradas que define GCC (en una máquina X86-64):

:| gcc -dM -E -x c - |grep -i endian #define __LITTLE_ENDIAN__ 1

En una máquina PPC obtengo:

:| gcc -dM -E -x c - |grep -i endian #define __BIG_ENDIAN__ 1 #define _BIG_ENDIAN 1

(El :| gcc -dM -E -xc - magic imprime todas las macros incorporadas).


No me gusta el método basado en el tipo de juego de palabras: a menudo el compilador lo advierte. ¡Para eso están los sindicatos!

int is_big_endian(void) { union { uint32_t i; char c[4]; } bint = {0x01020304}; return bint.c[0] == 1; }

El principio es equivalente al tipo de caso sugerido por otros, pero esto es más claro, y de acuerdo con C99, se garantiza que es correcto. gcc prefiere esto comparado con el lanzamiento directo del puntero.

Esto también es mucho mejor que arreglar el endianness en tiempo de compilación. Para el sistema operativo que admite arquitectura múltiple (binario grueso en Mac OS x por ejemplo), esto funcionará para ppc / i386, mientras que es muy fácil estropear las cosas .


Para obtener más detalles, es posible que desee consultar este artículo del proyecto de código Conceptos básicos sobre Endianness :

¿Cómo probar dinámicamente para el tipo Endian en tiempo de ejecución?

Como se explicó en Preguntas frecuentes sobre la animación por computadora, puede usar la siguiente función para ver si su código se está ejecutando en un sistema Little-o Big-Endian: Contraer

#define BIG_ENDIAN 0 #define LITTLE_ENDIAN 1

int TestByteOrder() { short int word = 0x0001; char *byte = (char *) &word; return(byte[0] ? LITTLE_ENDIAN : BIG_ENDIAN); }

Este código asigna el valor 0001h a un entero de 16 bits. A continuación, se asigna un puntero a punto en el primer byte (menos significativo) del valor entero. Si el primer byte del entero es 0x01h, entonces el sistema es Little-Endian (el 0x01h está en la dirección más baja o menos significativa). Si es 0x00h, entonces el sistema es Big-Endian.


Por favor mira este artículo :

Aquí hay un código para determinar cuál es el tipo de su máquina

int num = 1; if(*(char *)&num == 1) { printf("/nLittle-Endian/n"); } else { printf("Big-Endian/n"); }


Puede hacerlo configurando un int y enmascarando bits, pero probablemente la forma más fácil es simplemente usar las operaciones de conversión de byte de red incorporadas (ya que el orden de bytes de la red siempre es big endian).

if ( htonl(47) == 47 ) { // Big endian } else { // Little endian. }

El toqueteo de bits podría ser más rápido, pero de esta manera es simple, directo y bastante imposible de perder.


Puede usar std::endian si tiene acceso a C ++ 20:

#include <type_traits> if constexpr (std::endian::native == std::endian::big) { // Big endian system } else if constexpr (std::endian::native == std::endian::little) { // Little endian system } else { // Something else }


Si no quiere una compilación condicional, puede escribir el código independiente Endian. Aquí hay un ejemplo (tomado de Rob Pike ):

Leyendo un entero almacenado en little-endian en el disco, de una manera endian independiente:

i = (data[0]<<0) | (data[1]<<8) | (data[2]<<16) | (data[3]<<24);

El mismo código, tratando de tener en cuenta el endianness de la máquina:

i = *((int*)data); #ifdef BIG_ENDIAN /* swap the bytes */ i = ((i&0xFF)<<24) | (((i>>8)&0xFF)<<16) | (((i>>16)&0xFF)<<8) | (((i>>24)&0xFF)<<0); #endif


También puede hacerlo a través del preprocesador usando algo como el archivo de encabezado boost que se puede encontrar boost endian


compile time, non-macro, C ++ 11 constexpr solution:

union { uint16_t s; unsigned char c[2]; } constexpr static d {1}; constexpr bool is_little_endian() { return d.c[0] == 1; }


no probado, pero en mi opinión, esto debería funcionar? porque será 0x01 en little endian, y 0x00 en big endian?

bool runtimeIsLittleEndian(void) { volatile uint16_t i=1; return ((uint8_t*)&i)[0]==0x01;//0x01=little, 0x00=big }


bool isBigEndian() { static const uint16_t m_endianCheck(0x00ff); return ( *((uint8_t*)&m_endianCheck) == 0x0); }


int i=1; char *c=(char*)&i; bool littleendian=c;


union { int i; char c[sizeof(int)]; } x; x.i = 1; if(x.c[0] == 1) printf("little-endian/n"); else printf("big-endian/n");

Esta es otra solución. Similar a la solución de Andrew Hare.