C Definizione di macro per determinare la macchina big endian o little endian?


107

Esiste una definizione di macro di una riga per determinare l'endianità della macchina. Sto usando il seguente codice ma convertirlo in macro sarebbe troppo lungo.

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

    return (test_endian[0] == 0);
}

2
Perché non includere lo stesso codice in una macro?
dente di ferita

4
Non è possibile determinare in modo portabile l'endianness con il solo preprocessore C. Vuoi anche al 0posto del NULLtuo test finale, e cambia uno degli test_endianoggetti in qualcos'altro :-).
Alok Singhal,

2
Anche perché è necessaria una macro? La funzione inline farebbe lo stesso ed è molto più sicura.
punta di diamante il

13
@Sharptooth, una macro è interessante perché il suo valore può essere noto in fase di compilazione, il che significa che potresti usare l'endianness della tua piattaforma per controllare l'istanziazione del modello, ad esempio, o forse anche selezionare diversi blocchi di codice con una #ifdirettiva.
Rob Kennedy

3
È vero, ma inefficiente. Se ho una cpu little-endian e sto scrivendo dati little-endian sul cavo o su un file, preferirei di gran lunga evitare di decomprimere e reimballare i dati senza scopo. Scrivevo driver video per vivere. È estremamente importante quando si scrivono pixel su una scheda video per ottimizzare ogni posizione possibile.
Edward Falk,

Risposte:


102

Codice che supporta ordini di byte arbitrari, pronto per essere inserito in un file chiamato 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

Dovresti controllare i sistemi little endian tramite

O32_HOST_ORDER == O32_LITTLE_ENDIAN

11
Tuttavia, questo non ti consente di decidere endianness fino al runtime. Quanto segue non viene compilato perché. / ** isLittleEndian :: result -> 0 o 1 * / struct isLittleEndian {enum isLittleEndianResult {result = (O32_HOST_ORDER == O32_LITTLE_ENDIAN)}; };
user48956

3
È impossibile ottenere risultati fino al runtime?
k06a

8
Perché char? Migliore utilizzo uint8_te fallimento se questo tipo non è disponibile (che può essere verificato da #if UINT8_MAX). Nota che CHAR_BITè indipendente da uint8_t.
Andreas Spindler


3
Consentitemi di lanciarne un altro nel mix, per completezza:O32_HONEYWELL_ENDIAN = 0x02030001ul /* Honeywell 316 */
Edward Falk

49

Se hai un compilatore che supporta i letterali composti 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)

In generale, però, dovresti provare a scrivere codice che non dipenda dall'endianità della piattaforma host.


Esempio di implementazione indipendente dall'host endian di 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];
}

3
"dovresti provare a scrivere codice che non dipenda dall'endianità della piattaforma host". Sfortunatamente il mio appello, "So che stiamo scrivendo un livello di compatibilità POSIX, ma non voglio implementare ntoh, perché dipende dall'endianness della piattaforma host" è sempre caduto nel vuoto ;-). La gestione del formato grafico e il codice di conversione è l'altro principale candidato che ho visto: non vuoi basare tutto sul chiamare ntohl tutto il tempo.
Steve Jessop,

5
Puoi implementarlo ntohlin un modo che non dipende dall'endianità della piattaforma host.
caf

1
@caf come scriveresti ntohl in modo indipendente dall'host endianness?
Hayri Uğur Koltuk

3
@ AliVeli: ho aggiunto un'implementazione di esempio alla risposta.
caf

6
Dovrei anche aggiungere per la cronaca che "(* (uint16_t *)" \ 0 \ xff "<0x100)" non si compilerà in una costante, non importa quanto ottimizzo, almeno con gcc 4.5.2. Crea sempre codice eseguibile.
Edward Falk

43

Non esiste uno standard, ma su molti sistemi incluso <endian.h>ti fornirà alcune definizioni da cercare.


30
Testa l'endianness con #if __BYTE_ORDER == __LITTLE_ENDIANe #elif __BYTE_ORDER == __BIG_ENDIAN. E genera un #erroraltro modo.
Fino

6
<endian.h>non è disponibile su Windows
rustyx

2
I progetti Android e Chromium utilizzano a endian.hmeno che __APPLE__o non _WIN32sia definito.
patryk.beza

1
In OpenBSD 6.3, <endian.h> fornisce #if BYTE_ORDER == LITTLE_ENDIAN(o BIG_ENDIAN) senza trattini bassi prima dei nomi. _BYTE_ORDERè solo per le intestazioni di sistema. __BYTE_ORDERnon esiste.
George Koehler

@ To1ne Dubito che Endianness sia rilevante per Windows, poiché Windows (almeno attualmente) funziona solo su macchine x86 e ARM. x86 essendo sempre LE e ARM configurabile per utilizzare entrambe le architetture.
SimonC

27

Per rilevare endianness in fase di esecuzione, devi essere in grado di fare riferimento alla memoria. Se ti attieni allo standard C, la dichiarazione di una variabile in memoria richiede un'istruzione, ma la restituzione di un valore richiede un'espressione. Non so come farlo in una singola macro: questo è il motivo per cui gcc ha estensioni :-)

Se desideri avere un file .h, puoi definire

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

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

e poi puoi usare la ENDIANNESSmacro come preferisci.


6
Mi piace perché riconosce l'esistenza di endianness diverse dal piccolo e dal grande.
Alok Singhal,

6
A proposito, potrebbe valere la pena chiamare la macro INT_ENDIANNESS, o anche UINT32_T_ENDIANNESS, poiché verifica solo la rappresentazione di archiviazione di un tipo. C'è un ARM ABI in cui i tipi integrali sono little-endian, ma i doppi sono middle-endian (ogni parola è little-endian, ma la parola con il bit di segno viene prima dell'altra parola). Questo ha causato un po 'di eccitazione nel team del compilatore per un giorno o giù di lì, te lo posso dire.
Steve Jessop,

19

Se vuoi fare affidamento solo sul preprocessore, devi capire l'elenco dei simboli predefiniti. L'aritmetica del preprocessore non ha il concetto di indirizzamento.

GCC su Mac definisce __LITTLE_ENDIAN__o__BIG_ENDIAN__

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

Quindi, puoi aggiungere più direttive condizionali del preprocessore basate sul rilevamento della piattaforma come #ifdef _WIN32ecc.


6
GCC 4.1.2 su Linux non sembra definire quelle macro, sebbene GCC 4.0.1 e 4.2.1 le definiscano su Macintosh. Quindi non è un metodo affidabile per lo sviluppo multipiattaforma, anche quando è consentito stabilire quale compilatore utilizzare.
Rob Kennedy

1
oh sì è perché è definito solo da GCC su Mac.
Gregory Pakosz

Nota: il mio GCC (su Mac) definisce #define __BIG_ENDIAN__ 1e #define _BIG_ENDIAN 1.

clang 5.0.1 per OpenBSD / amd64 ha #define __LITTLE_ENDIAN__ 1. Questa macro sembra essere una funzione clang, non una funzione gcc. Il gcccomando in alcuni Mac non è gcc, è clang.
George Koehler

GCC 4.2.1 su Mac allora era GCC
Gregory Pakosz

15

Credo che questo sia ciò che è stato chiesto. L'ho provato solo su una piccola macchina endian sotto msvc. Qualcuno può confermare su una macchina 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

Come nota a margine (specifica del compilatore), con un compilatore aggressivo puoi usare l'ottimizzazione "eliminazione codice morto" per ottenere lo stesso effetto di un tempo di compilazione #ifcome questo:

    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; // ?
    }

Quanto sopra si basa sul fatto che il compilatore riconosce i valori costanti in fase di compilazione, rimuove completamente il codice all'interno if (false) { ... }e lo sostituisce come if (true) { foo(); }con foo();Lo scenario peggiore: il compilatore non esegue l'ottimizzazione, si ottiene comunque il codice corretto ma un po 'più lento.


Mi piace questo metodo, ma correggimi se sbaglio: funziona solo quando stai compilando sulla macchina per cui stai costruendo, giusto?
leetNightshade

3
gcc genera anche un errore a causa delle costanti di caratteri multi-carattere. Quindi, non portatile.
Edward Falk

2
quale compilatore ti permette di scrivere 'ABCD'?
Ryan Haining

2
Molti compilatori consentiranno costanti di caratteri multibyte in modalità di conformità rilassate, ma clang -Wpedantic -Werror -Wall -ansi foo.ceseguiranno la parte superiore con e si verificherà un errore. (Clang e questo in particolare: -Wfour-char-constants -Werror)

@Edward Falk Non è un errore avere una costante multi-carattere nel codice. È un comportamento definito dall'implementazione C11 6.4.4.4. 10. gcc e altri potrebbero / non possono segnalare / errore a seconda delle impostazioni, ma non è un errore C. Certamente non è popolare usare costanti di caratteri multi-carattere.
chux - Ripristina Monica il

10

Se stai cercando un test in fase di compilazione e stai usando gcc, puoi fare:

#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__

Vedere la documentazione di gcc per ulteriori informazioni.


3
Questa è sicuramente la migliore risposta per chiunque utilizzi gcc
rtpax

2
__BYTE_ORDER__è disponibile da GCC 4.6
Benoit Blanchon

8

È possibile nell'accesso infatti il ricordo di un oggetto temporaneo utilizzando un letterale composto (C99):

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

Quale GCC valuterà in fase di compilazione.


Mi piace. Esiste un modo portatile in fase di compilazione per sapere che stai compilando in C99?
Edward Falk,

1
Oh, e se non fosse GCC?
Edward Falk

1
@EdwardFalk Sì. #if __STDC_VERSION__ >= 199901L.
Jens

7

La 'libreria di rete C' offre funzioni per gestire endian'ness. Vale a dire htons (), htonl (), ntohs () e ntohl () ... dove n è "network" (cioè big-endian) e h è "host" (cioè l'endianità della macchina che esegue il codice).

Queste apparenti 'funzioni' sono (comunemente) definite come macro [vedere <netinet / in.h>], quindi non c'è alcun sovraccarico di runtime per usarle.

Le seguenti macro usano queste 'funzioni' per valutare endian'ness.

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

Inoltre:

L'unica volta che ho bisogno di conoscere l'endianità di un sistemaèquando scrivo una variabile [in un file / altro] che può essere letta da un altro sistema di endianità sconosciuta (per compatibilità multipiattaforma ) ... In casi come questi, potresti preferire utilizzare direttamente le funzioni endian:

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

Questo in realtà non risponde alla domanda che stava cercando un modo rapido per determinare l'endianness.
Oren

@Oren: Rispetto alla tua valida critica, ho anteposto dettagli che affrontano più direttamente la domanda originale.
BlueChip

6

Usa una funzione inline piuttosto che una macro. Inoltre, è necessario memorizzare qualcosa in memoria che è un effetto collaterale non così piacevole di una macro.

Puoi convertirlo in una breve macro utilizzando una variabile statica o globale, come questa:

static int s_endianess = 0;
#define ENDIANESS() ((s_endianess = 1), (*(unsigned char*) &s_endianess) == 0)

Penso che questo sia il migliore poiché è il più semplice. tuttavia non prova contro endian misto
Hayri Uğur Koltuk

1
Perché non è s_endianessimpostato su 1 per iniziare?
SquareRootOfTwentyThree

5

Sebbene non ci sia un #define portatile o qualcosa su cui fare affidamento, le piattaforme forniscono funzioni standard per la conversione da e verso il tuo endian 'host'.

Generalmente, fai l'archiviazione - su disco o in rete - usando 'network endian', che è BIG endian, e il calcolo locale usando host endian (che su x86 è LITTLE endian). Usi htons()e ntohs()e gli amici per convertire tra i due.


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

6
Questo genera anche codice eseguibile, non una costante. Non puoi fare "#if IS_BIG_ENDIAN"
Edward Falk

Mi piace questa soluzione poiché non si basa su un comportamento indefinito degli standard C / C ++, per quanto ne so. Non è tempo di compilazione ma l'unica soluzione standard è in attesa di c ++ 20 std :: endian
ceztko

4

Non dimenticare che endianness non è l'intera storia: la dimensione di charpotrebbe non essere 8 bit (ad esempio DSP), la negazione del complemento a due non è garantita (ad esempio Cray), potrebbe essere richiesto un allineamento rigoroso (ad esempio SPARC, anche ARM balza al centro -endian quando non allineato), ecc. ecc.

Potrebbe essere un'idea migliore invece indirizzare una specifica architettura della CPU .

Per esempio:

#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
}

Si noti che questa soluzione non è purtroppo ultraportatile, poiché dipende dalle definizioni specifiche del compilatore (non esiste uno standard, ma ecco una bella raccolta di tali definizioni).


3

Prova questo:

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

   TEST;
}

2

Si prega di prestare attenzione che la maggior parte delle risposte qui non sono portabili, poiché i compilatori oggi valuteranno quelle risposte in tempo di compilazione (dipende dall'ottimizzazione) e restituiranno un valore specifico basato su uno specifico endianness, mentre l'effettivo endianness della macchina può differire. I valori su cui viene testato l'endianness, non raggiungeranno mai la memoria di sistema quindi il codice reale eseguito restituirà lo stesso risultato indipendentemente dall'endianness effettivo.

Ad esempio , in ARM Cortex-M3 l'endianness implementato si rifletterà in un bit di stato AIRCR.ENDIANNESS e il compilatore non può conoscere questo valore in fase di compilazione.

Risultato della compilazione per alcune delle risposte suggerite qui:

https://godbolt.org/z/GJGNE2 per questa risposta,

https://godbolt.org/z/Yv-pyJ per questa risposta e così via.

Per risolverlo dovrai usare il volatilequalificatore. Yogeesh H T's risposta è quello più vicino per l'utilizzo di vita reale di oggi, ma dal momento che Christophsuggerisce la soluzione più completa, una lieve correzione al suo risposta sarebbe la risposta completa, basta aggiungere volatilela dichiarazione dell'Unione: static const volatile union.

Ciò assicurerebbe l'archiviazione e la lettura dalla memoria, che è necessaria per determinare l'endianness.


2

Se esegui il dump del preprocessore #defines

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

Di solito puoi trovare cose che ti aiuteranno. Con logica del tempo di compilazione.

#define __LITTLE_ENDIAN__ 1
#define __BYTE_ORDER__ __ORDER_LITTLE_ENDIAN__

Tuttavia, vari compilatori possono avere definizioni diverse.


0

La mia risposta non è come chiesto ma è davvero semplice scoprire se il tuo sistema è little endian o big endian?

Codice:

#include<stdio.h>

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

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

0

C Codice per verificare se un sistema è 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");

-3

Macro per trovare 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;
}

3
La prima macro non è corretta e restituirà sempre "Big-Endian". Lo spostamento di bit non è influenzato dall'endianness - l'endianness influenza solo le letture e le memorizzazioni nella memoria.
GaspardP
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.