C ¿Definición de macro para determinar la máquina big endian o little endian?

107

¿Existe una definición de macro de una línea para determinar el endianness de la máquina? Estoy usando el siguiente código, pero convertirlo a macro sería demasiado largo.

unsigned char test_endian( void )
{
    int test_var = 1;
    unsigned char *test_endian = (unsigned char*)&test_var;

    return (test_endian[0] == 0);
}
manav mn
fuente
2
¿Por qué no incluir el mismo código en una macro?
diente afilado
4
No se puede determinar la endianidad de forma portátil con el preprocesador de C solo. También desea en 0lugar de NULLen su prueba final, y cambiar uno de los test_endianobjetos a otra cosa :-).
Alok Singhal
2
Además, ¿por qué es necesaria una macro? La función en línea haría lo mismo y es mucho más segura.
diente afilado
13
@Sharptooth, una macro es atractiva porque su valor puede conocerse en el momento de la compilación, lo que significa que podría usar el endianness de su plataforma para controlar la creación de instancias de plantillas, por ejemplo, o tal vez incluso seleccionar diferentes bloques de código con una #ifdirectiva.
Rob Kennedy
3
Eso es cierto, pero ineficiente. Si tengo una cpu little-endian y estoy escribiendo datos little-endian en el cable o en un archivo, prefiero evitar desempaquetar y volver a empaquetar datos sin ningún propósito. Solía ​​escribir controladores de video para ganarme la vida. Es extremadamente importante al escribir píxeles en una tarjeta de video para optimizar cada lugar que pueda.
Edward Falk

Respuestas:

102

Código que admite órdenes de bytes arbitrarias, listo para colocarse en un archivo llamado order32.h:

#ifndef ORDER32_H
#define ORDER32_H

#include <limits.h>
#include <stdint.h>

#if CHAR_BIT != 8
#error "unsupported char size"
#endif

enum
{
    O32_LITTLE_ENDIAN = 0x03020100ul,
    O32_BIG_ENDIAN = 0x00010203ul,
    O32_PDP_ENDIAN = 0x01000302ul,      /* DEC PDP-11 (aka ENDIAN_LITTLE_WORD) */
    O32_HONEYWELL_ENDIAN = 0x02030001ul /* Honeywell 316 (aka ENDIAN_BIG_WORD) */
};

static const union { unsigned char bytes[4]; uint32_t value; } o32_host_order =
    { { 0, 1, 2, 3 } };

#define O32_HOST_ORDER (o32_host_order.value)

#endif

Verificaría sistemas little endian a través de

O32_HOST_ORDER == O32_LITTLE_ENDIAN
Christoph
fuente
11
Sin embargo, esto no le permite decidir endian-ness hasta el tiempo de ejecución. Lo siguiente no se puede compilar porque. / ** isLittleEndian :: result -> 0 o 1 * / struct isLittleEndian {enum isLittleEndianResult {result = (O32_HOST_ORDER == O32_LITTLE_ENDIAN)}; };
user48956
3
¿Es imposible obtener resultados hasta el tiempo de ejecución?
k06a
8
¿Por qué char? Mejor uso uint8_ty falla si este tipo no está disponible (que puede ser verificado por #if UINT8_MAX). Tenga en cuenta que CHAR_BITes independiente de uint8_t.
Andreas Spindler
2
Esto es UB en c ++: stackoverflow.com/questions/11373203/…
Lyberta
3
Permítanme agregar uno más a la mezcla, para completar:O32_HONEYWELL_ENDIAN = 0x02030001ul /* Honeywell 316 */
Edward Falk
49

Si tiene un compilador que admita literales compuestos C99:

#define IS_BIG_ENDIAN (!*(unsigned char *)&(uint16_t){1})

o:

#define IS_BIG_ENDIAN (!(union { uint16_t u16; unsigned char c; }){ .u16 = 1 }.c)

Sin embargo, en general, debe intentar escribir código que no dependa del endianness de la plataforma host.


Ejemplo de implementación de host-endianness-independiente de ntohl():

uint32_t ntohl(uint32_t n)
{
    unsigned char *np = (unsigned char *)&n;

    return ((uint32_t)np[0] << 24) |
        ((uint32_t)np[1] << 16) |
        ((uint32_t)np[2] << 8) |
        (uint32_t)np[3];
}
coste y flete
fuente
3
"debería intentar escribir código que no dependa de la endianidad de la plataforma de acogida". Desafortunadamente, mi súplica, "Sé que estamos escribiendo una capa de compatibilidad POSIX, pero no quiero implementar ntoh, porque depende de la endianidad de la plataforma host" siempre cayó en oídos sordos ;-). El manejo de formato de gráficos y el código de conversión es el otro candidato principal que he visto: no desea basar todo en llamar a ntohl todo el tiempo.
Steve Jessop
5
Puede implementar ntohlde una manera que no dependa del endianness de la plataforma de host.
caf
1
@caf ¿cómo escribirías ntohl de una manera independiente de host-endianness?
Hayri Uğur Koltuk
3
@AliVeli: He agregado una implementación de ejemplo a la respuesta.
caf
6
También debo agregar para el registro, que "(* (uint16_t *)" \ 0 \ xff "<0x100)" no se compilará en una constante, no importa cuánto optimice, al menos con gcc 4.5.2. Siempre crea código ejecutable.
Edward Falk
43

No existe un estándar, pero en muchos sistemas, incluso <endian.h>le dará algunas definiciones para buscar.

Ignacio Vázquez-Abrams
fuente
30
Pruebe la endianidad con #if __BYTE_ORDER == __LITTLE_ENDIANy #elif __BYTE_ORDER == __BIG_ENDIAN. Y generar un #errorelsewise.
Hasta
6
<endian.h>no está disponible en Windows
rustyx
2
Los proyectos de Android y Chromium se utilizan a endian.hmenos que se defina __APPLE__o _WIN32.
patryk.beza
1
En OpenBSD 6.3, <endian.h> proporciona #if BYTE_ORDER == LITTLE_ENDIAN(o BIG_ENDIAN) sin guiones bajos antes de los nombres. _BYTE_ORDERes solo para encabezados del sistema. __BYTE_ORDERno existe.
George Koehler
@ To1ne Dudo que Endianness sea relevante para Windows, ya que Windows (al menos actualmente) se ejecuta solo en máquinas x86 y ARM. x86 siempre es LE y ARM es configurable para usar cualquier arquitectura.
SimonC
27

Para detectar endianness en tiempo de ejecución, debe poder hacer referencia a la memoria. Si se apega al estándar C, declarar una variable en la memoria requiere una declaración, pero devolver un valor requiere una expresión. No sé cómo hacer esto en una sola macro; es por eso que gcc tiene extensiones :-)

Si está dispuesto a tener un archivo .h, puede definir

static uint32_t endianness = 0xdeadbeef; 
enum endianness { BIG, LITTLE };

#define ENDIANNESS ( *(const char *)&endianness == 0xef ? LITTLE \
                   : *(const char *)&endianness == 0xde ? BIG \
                   : assert(0))

y luego puedes usar la ENDIANNESSmacro como quieras.

Norman Ramsey
fuente
6
Me gusta esto porque reconoce la existencia de un endianismo diferente al pequeño y al grande.
Alok Singhal
6
Hablando de eso, podría valer la pena llamar a la macro INT_ENDIANNESS, o incluso UINT32_T_ENDIANNESS, ya que solo prueba la representación de almacenamiento de un tipo. Hay un ARM ABI donde los tipos integrales son little-endian, pero los dobles son de medio-endian (cada palabra es little-endian, pero la palabra con el bit de signo viene antes de la otra palabra). Eso causó cierto entusiasmo entre el equipo del compilador durante un día o dos, se lo puedo asegurar.
Steve Jessop
19

Si solo desea confiar en el preprocesador, debe averiguar la lista de símbolos predefinidos. La aritmética del preprocesador no tiene el concepto de direccionamiento.

GCC en Mac define __LITTLE_ENDIAN__o__BIG_ENDIAN__

$ gcc -E -dM - < /dev/null |grep ENDIAN
#define __LITTLE_ENDIAN__ 1

Luego, puede agregar más directivas condicionales de preprocesador basadas en la detección de plataforma, como #ifdef _WIN32etc.

Gregory Pakosz
fuente
6
GCC 4.1.2 en Linux no parece definir esas macros, aunque GCC 4.0.1 y 4.2.1 las definen en Macintosh. Por lo tanto, no es un método confiable para el desarrollo multiplataforma, incluso cuando se le permite dictar qué compilador usar.
Rob Kennedy
1
Oh, sí, es porque solo está definido por GCC en Mac.
Gregory Pakosz
Nota: Mi GCC (en Mac) define #define __BIG_ENDIAN__ 1y #define _BIG_ENDIAN 1.
clang 5.0.1 para OpenBSD / amd64 tiene #define __LITTLE_ENDIAN__ 1. Esta macro parece ser una característica de clang, no una característica de gcc. El gcccomando en algunas Mac no es gcc, es clang.
George Koehler
GCC 4.2.1 en Mac era GCC en ese entonces
Gregory Pakosz
15

Creo que esto es lo que se pidió. Solo probé esto en una pequeña máquina endian bajo msvc. Alguien por favor confirme en una máquina Big Endian.

    #define LITTLE_ENDIAN 0x41424344UL 
    #define BIG_ENDIAN    0x44434241UL
    #define PDP_ENDIAN    0x42414443UL
    #define ENDIAN_ORDER  ('ABCD') 

    #if ENDIAN_ORDER==LITTLE_ENDIAN
        #error "machine is little endian"
    #elif ENDIAN_ORDER==BIG_ENDIAN
        #error "machine is big endian"
    #elif ENDIAN_ORDER==PDP_ENDIAN
        #error "jeez, machine is PDP!"
    #else
        #error "What kind of hardware is this?!"
    #endif

Como nota al margen (específica del compilador), con un compilador agresivo puede usar la optimización de "eliminación de código muerto" para lograr el mismo efecto que un tiempo de compilación #ifcomo este:

    unsigned yourOwnEndianSpecific_htonl(unsigned n)
    {
        static unsigned long signature= 0x01020304UL; 
        if (1 == (unsigned char&)signature) // big endian
            return n;
        if (2 == (unsigned char&)signature) // the PDP style
        {
            n = ((n << 8) & 0xFF00FF00UL) | ((n>>8) & 0x00FF00FFUL);
            return n;
        }
        if (4 == (unsigned char&)signature) // little endian
        {
            n = (n << 16) | (n >> 16);
            n = ((n << 8) & 0xFF00FF00UL) | ((n>>8) & 0x00FF00FFUL);
            return n;
        }
        // only weird machines get here
        return n; // ?
    }

Lo anterior se basa en el hecho de que el compilador reconoce los valores constantes en el momento de la compilación, elimina por completo el código interno if (false) { ... }y reemplaza el código como if (true) { foo(); }en foo();el peor de los casos: el compilador no realiza la optimización, aún obtiene el código correcto pero un poco más lento.

ggpp23
fuente
Me gusta este método, pero corrígeme si me equivoco: esto solo funciona cuando estás compilando en la máquina para la que estás construyendo, ¿correcto?
leetNightshade
3
gcc también arroja un error debido a constantes de caracteres de varios caracteres. Por lo tanto, no portátil.
Edward Falk
2
¿Qué compilador te deja escribir 'ABCD'?
Ryan Haining
2
Muchos compiladores permitirán constantes de caracteres multibyte en modos de cumplimiento relajados, pero ejecuten la parte superior con clang -Wpedantic -Werror -Wall -ansi foo.cy se producirá un error. (Clang y esto específicamente: -Wfour-char-constants -Werror)
@Edward Falk No es un error tener una constante de varios caracteres en el código. Es un comportamiento definido por la implementación C11 6.4.4.4. 10. gcc y otros pueden / pueden no advertir / error dependiendo de la configuración, pero no es un error C. Ciertamente no es popular utilizar constantes de caracteres de varios caracteres.
chux - Reincorporar a Monica
10

Si está buscando una prueba de tiempo de compilación y está usando gcc, puede hacer:

#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__

Consulte la documentación de gcc para obtener más información.

Jérôme Pouiller
fuente
3
Esta es definitivamente la mejor respuesta para cualquiera que use gcc
rtpax
2
__BYTE_ORDER__está disponible desde GCC 4.6
Benoit Blanchon
8

De hecho, puede acceder a la memoria de un objeto temporal utilizando un literal compuesto (C99):

#define IS_LITTLE_ENDIAN (1 == *(unsigned char *)&(const int){1})

Qué GCC evaluará en el momento de la compilación.

u0b34a0f6ae
fuente
Me gusta. ¿Existe una forma portátil y en tiempo de compilación de saber que está compilando bajo C99?
Edward Falk
1
Ah, ¿y si no es GCC?
Edward Falk
1
@EdwardFalk Sí. #if __STDC_VERSION__ >= 199901L.
Jens
7

La 'biblioteca de red C' ofrece funciones para manejar endian'ness. Es decir, htons (), htonl (), ntohs () y ntohl () ... donde n es "red" (es decir, big-endian) y h es "host" (es decir, el endian'ness de la máquina que ejecuta el código).

Estas 'funciones' aparentes se definen (comúnmente) como macros [consulte <netinet / in.h>], por lo que no hay una sobrecarga de tiempo de ejecución para usarlas.

Las siguientes macros utilizan estas 'funciones' para evaluar la endianidad.

#include <arpa/inet.h>
#define  IS_BIG_ENDIAN     (1 == htons(1))
#define  IS_LITTLE_ENDIAN  (!IS_BIG_ENDIAN)

Adicionalmente:

La única vez que necesito saber la endianidad de un sistema es cuando escribo una variable [en un archivo / otro] que puede ser leída por otro sistema de endianidad desconocida (para compatibilidad multiplataforma ) ... En casos como estos, es posible que prefiera utilizar las funciones endian directamente:

#include <arpa/inet.h>

#define JPEG_MAGIC  (('J'<<24) | ('F'<<16) | ('I'<<8) | 'F')

// Result will be in 'host' byte-order
unsigned long  jpeg_magic = JPEG_MAGIC;

// Result will be in 'network' byte-order (IE. Big-Endian/Human-Readable)
unsigned long  jpeg_magic = htonl(JPEG_MAGIC);
BlueChip
fuente
Esto realmente no responde a la pregunta que buscaba una forma rápida de determinar la endianidad.
Oren
@Oren: Con respecto a su crítica válida, he añadido un detalle que aborda la pregunta original de manera más directa.
BlueChip
6

Utilice una función en línea en lugar de una macro. Además, necesita almacenar algo en la memoria que es un efecto secundario no tan agradable de una macro.

Puede convertirlo en una macro corta usando una variable estática o global, como esta:

static int s_endianess = 0;
#define ENDIANESS() ((s_endianess = 1), (*(unsigned char*) &s_endianess) == 0)
user231967
fuente
Creo que este es el mejor ya que es el más simple. sin embargo, no prueba contra endian mixto
Hayri Uğur Koltuk
1
¿Por qué no se s_endianessestablece en 1 para empezar?
SquareRootOfTwentyThree
5

Si bien no hay un #define portátil o algo en lo que confiar, las plataformas proporcionan funciones estándar para convertir hacia y desde su endian 'host'.

Por lo general, el almacenamiento (en disco o en la red) se realiza mediante 'network endian', que es BIG endian, y computación local mediante host endian (que en x86 es LITTLE endian). Usas htons()y ntohs()y amigos para convertir entre los dos.

Será
fuente
4
#include <stdint.h>
#define IS_LITTLE_ENDIAN (*(uint16_t*)"\0\1">>8)
#define IS_BIG_ENDIAN (*(uint16_t*)"\1\0">>8)

fuente
6
Esto también genera código ejecutable, no una constante. No podrías hacer "#if IS_BIG_ENDIAN"
Edward Falk
Me gusta esta solución ya que no se basa en el comportamiento indefinido de los estándares C / C ++, hasta donde tengo entendido. No es tiempo de compilación, pero la única solución estándar para eso es esperar c ++ 20 std :: endian
ceztko
4

No olvide que el endianness no es toda la historia: el tamaño de charpodría no ser de 8 bits (por ejemplo, DSP), la negación del complemento a dos no está garantizada (por ejemplo, Cray), es posible que se requiera una alineación estricta (por ejemplo, SPARC, también ARM se coloca en el medio -endian cuando no está alineado), etc., etc.

En su lugar, podría ser una mejor idea apuntar a una arquitectura de CPU específica .

Por ejemplo:

#if defined(__i386__) || defined(_M_IX86) || defined(_M_IX64)
  #define USE_LITTLE_ENDIAN_IMPL
#endif

void my_func()
{
#ifdef USE_LITTLE_ENDIAN_IMPL
  // Intel x86-optimized, LE implementation
#else
  // slow but safe implementation
#endif
}

Tenga en cuenta que, lamentablemente, esta solución tampoco es ultraportátil, ya que depende de definiciones específicas del compilador (no existe un estándar, pero aquí hay una buena compilación de tales definiciones).

rustyx
fuente
3

Prueba esto:

#include<stdio.h>        
int x=1;
#define TEST (*(char*)&(x)==1)?printf("little endian"):printf("Big endian")
int main()
{

   TEST;
}
Prasoon Saurav
fuente
2

Preste atención a que la mayoría de las respuestas aquí no son portátiles, ya que los compiladores de hoy evaluarán esas respuestas en tiempo de compilación (depende de la optimización) y devolverán un valor específico basado en una endianidad específica, mientras que la endianidad real de la máquina puede diferir. Los valores en los que se prueba el endianness nunca llegarán a la memoria del sistema, por lo que el código ejecutado real devolverá el mismo resultado independientemente del endianness real.

Por ejemplo , en ARM Cortex-M3 la endianidad implementada se reflejará en un bit de estado AIRCR.ENDIANNESS y el compilador no puede conocer este valor en tiempo de compilación.

Salida de compilación para algunas de las respuestas sugeridas aquí:

https://godbolt.org/z/GJGNE2 para esta respuesta,

https://godbolt.org/z/Yv-pyJ para esto respuesta, y así sucesivamente.

Para resolverlo, necesitará usar el volatilecalificador. Yogeesh H T's respuesta es la más cercana para el uso de la vida real de hoy, pero como Christophsugiere solución más completa, una ligera corrección a su respuesta sería la respuesta completa, sólo tiene que añadir volatilea la declaración de la Unión: static const volatile union.

Esto aseguraría el almacenamiento y la lectura de la memoria, que es necesaria para determinar la endianidad.

usuario2162550
fuente
2

Si vuelca el preprocesador #defines

gcc -dM -E - < /dev/null
g++ -dM -E -x c++ - < /dev/null

Por lo general, puede encontrar cosas que lo ayudarán. Con lógica de tiempo de compilación.

#define __LITTLE_ENDIAN__ 1
#define __BYTE_ORDER__ __ORDER_LITTLE_ENDIAN__

Sin embargo, varios compiladores pueden tener diferentes definiciones.

Sam P
fuente
0

Mi respuesta no es la que se le preguntó, pero ¿es realmente sencillo averiguar si su sistema es little endian o big endian?

Código:

#include<stdio.h>

int main()
{
  int a = 1;
  char *b;

  b = (char *)&a;
  if (*b)
    printf("Little Endian\n");
  else
    printf("Big Endian\n");
}
roottraveller
fuente
0

Código C para comprobar si un sistema es little-endian o big-indian.

int i = 7;
char* pc = (char*)(&i);
if (pc[0] == '\x7') // aliasing through char is ok
    puts("This system is little-endian");
else
    puts("This system is big-endian");
SM AMRAN
fuente
-3

Macro para encontrar endiannes

#define ENDIANNES() ((1 && 1 == 0) ? printf("Big-Endian"):printf("Little-Endian"))

o

#include <stdio.h>

#define ENDIAN() { \
volatile unsigned long ul = 1;\
volatile unsigned char *p;\
p = (volatile unsigned char *)&ul;\
if (*p == 1)\
puts("Little endian.");\
else if (*(p+(sizeof(unsigned long)-1)) == 1)\
puts("Big endian.");\
else puts("Unknown endian.");\
}

int main(void) 
{
       ENDIAN();
       return 0;
}
Yogeesh HT
fuente
3
La primera macro es incorrecta y siempre devolverá "Big-Endian". El cambio de bits no se ve afectado por la endianidad; la endianidad solo afecta las lecturas y las tiendas en la memoria.
GaspardP