Rilevamento di endianità a livello di codice in un programma C ++

C’è un modo programmatico per scoprire se sei o meno in un’architettura big-endian o little-endian? Devo essere in grado di scrivere codice che verrà eseguito su un sistema Intel o PPC e utilizzare esattamente lo stesso codice (cioè nessuna compilazione condizionale).

Non mi piace il metodo basato sul tipo puning – verrà spesso messo in guardia dal compilatore. Questo è esattamente ciò che i sindacati sono per!

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

Il principio è equivalente al caso tipo suggerito da altri, ma questo è più chiaro – e secondo C99, è garantito che sia corretto. gcc preferisce questo rispetto al cast puntatore diretto.

Questo è anche molto meglio che riparare il endianness in fase di compilazione – per sistemi operativi che supportano la multi-architettura (fat binary su Mac os x per esempio), questo funzionerà sia per ppc / i386, mentre è molto facile rovinare le cose altrimenti .

Puoi farlo impostando un int e mascherando i bit, ma probabilmente il modo più semplice è solo usare le operazioni di conversione dei byte di rete incorporate (dato che l’ordine dei byte di rete è sempre big endian).

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

Un po ‘di giocherellando potrebbe essere più veloce, ma in questo modo è semplice, diretto e praticamente imansible da incasinare.

Si prega di consultare questo articolo :

Ecco un codice per determinare qual è il tipo di macchina

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

Questo è normalmente fatto in fase di compilazione (specialmente per ragioni di prestazioni) usando i file header disponibili dal compilatore o creandone di tuoi. Su linux hai il file di intestazione “/usr/include/endian.h”

Puoi usare std::endian se hai accesso al compilatore C ++ 20 come GCC 8+ o Clang 7+:

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

Ehm … Mi sorprende che nessuno abbia capito che il compilatore semplicemente ottimizzerà il test e metterà un risultato fisso come valore di ritorno. Questo rende tutti gli esempi di codice sopra, effettivamente inutili. L’unica cosa che verrebbe restituita è l’endianness in fase di compilazione! E sì, ho testato tutti gli esempi di cui sopra. Ecco un esempio con MSVC 9.0 (Visual Studio 2008).

Codice Pure C

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

Sassembly

 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 : ui = 1; ; 18 : ; 19 : if (1 == uc[0]) ; 20 : return DNA_ENDIAN_LITTLE; mov eax, 1 ; 21 : else if (1 == uc[3]) ; 22 : return DNA_ENDIAN_BIG; ; 23 : else ; 24 : return DNA_ENDIAN_UNKNOWN; ; 25 : } ret _DNA_GetEndianness ENDP END 

Forse è ansible distriggersre QUALSIASI ottimizzazione in fase di compilazione solo per questa funzione, ma non lo so. Altrimenti è forse ansible codificarlo in assembly, anche se non è portatile. E anche allora potrebbe essere ottimizzato. Mi fa pensare che ho bisogno di un assemblatore davvero schifoso, implementare lo stesso codice per tutte le CPU / set di istruzioni esistenti e … beh, non importa.

Inoltre, qualcuno qui ha detto che l’endianità non cambia durante l’esecuzione. SBAGLIATO. Ci sono macchine bi-endiane là fuori. La loro endianità può variare l’esecuzione del durng. INOLTRE, non c’è solo Little Endian e Big Endian, ma anche altri endianness (che parola).

Odio e amo codificare allo stesso tempo …

Dichiara una variabile int:

 int variable = 0xFF; 

Adesso usa i puntatori char * in varie parti di esso e controlla cosa c’è in quelle parti.

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

In base a quale punto si punta a 0xFF byte ora è ansible rilevare endianness. Ciò richiede sizeof (int)> sizeof (char), ma è sicuramente vero per le piattaforms discusse.

Ho sorpreso nessuno ha menzionato le macro che il pre-processore definisce di default. Mentre questi varieranno a seconda della tua piattaforma; sono molto più puliti di dover scrivere il tuo proprio conto di endian.

Per esempio; se osserviamo i macro incorporati che GCC definisce (su una macchina X86-64):

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

Su una macchina PPC ottengo:

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

(Il :| gcc -dM -E -xc - magic stampa tutte le macro incorporate).

Per ulteriori dettagli, si consiglia di controllare questo articolo codeproject Concetti di base su Endianness :

Come testare dynamicmente il tipo di Endian in fase di esecuzione?

Come spiegato nelle FAQ di Animazione computer, puoi utilizzare la seguente funzione per verificare se il tuo codice è in esecuzione su un sistema Little o Big-Endian: Collapse

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

Questo codice assegna il valore 0001h a un intero a 16 bit. Un puntatore char viene quindi assegnato al punto sul primo byte (meno significativo) del valore intero. Se il primo byte del numero intero è 0x01h, il sistema è Little-Endian (il 0x01h è nell’indirizzo più basso o meno significativo). Se è 0x00h, il sistema è Big-Endian.

Come detto sopra, usa i trucchi sindacali.

Ci sono pochi problemi con quelli sopra suggeriti, ma soprattutto che l’accesso alla memoria non allineato è notoriamente lento per la maggior parte delle architetture, e alcuni compilatori non riconosceranno affatto tali predicati costanti, a meno che la parola non sia allineata.

Poiché il semplice test di endian è noioso, ecco la funzione (modello) che capovolge l’input / output di un intero arbitrario in base alle specifiche, indipendentemente dall’architettura dell’host.

 #include  #define BIG_ENDIAN 1 #define LITTLE_ENDIAN 0 template  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:

Per convertire da endian data a host, utilizzare:

host = endian(source, endian_of_source)

Per convertire da host endian a endian dato, utilizzare:

output = endian(hostsource, endian_you_want_to_output)

Il codice risultante è veloce quanto la scrittura di assembly a mano su clang, su gcc è più lento (srotolata e, <<, >>, | per ogni byte) ma è comunque decente.

A meno che tu non stia utilizzando un framework che è stato portato su processori PPC e Intel, dovrai eseguire compilazioni condizionali, poiché le piattaforms PPC e Intel hanno architetture hardware, pipeline, bus, ecc. Completamente differenti. Questo rende il codice assembly completamente diverso tra il due.

Per quanto riguarda la ricerca di endianness, procedi nel seguente modo:

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

Si otterrà tempChar per essere 0x12 o 0x34, da cui si conoscerà il endianness.

Il modo C ++ è stato quello di usare boost , in cui i controlli e i cast del preprocessore sono suddivisi in compartimenti all’interno di librerie molto ben testate.

La libreria Predef (boost / predef.h) riconosce quattro diversi tipi di endianness .

La libreria Endian è stata progettata per essere sottoposta allo standard C ++ e supporta un’ampia varietà di operazioni su dati sensibili agli endian.

Come affermato nelle risposte precedenti, Endianness farà parte di c ++ 20.

Farei qualcosa di simile a questo:

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

Lungo queste linee, si otterrebbe una funzione efficiente in termini di tempo che esegue il calcolo solo una volta.

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

compilare time, non-macro, soluzione constexpr C ++ 11:

 union { uint16_t s; unsigned char c[2]; } constexpr static d {1}; constexpr bool is_little_endian() { return dc[0] == 1; } 
 union { int i; char c[sizeof(int)]; } x; xi = 1; if(xc[0] == 1) printf("little-endian\n"); else printf("big-endian\n"); 

Questa è un’altra soluzione. Simile alla soluzione di Andrew Hare.

non testato, ma nella mia mente, questo dovrebbe funzionare? perché sarà 0x01 su little endian e 0x00 su big endian?

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

Puoi farlo anche tramite il preprocessore usando qualcosa come il file header di boost che può essere trovato boost endian

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

Cosa ne pensi di questo?

 #include  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 meno che l’intestazione endian non sia solo GCC, fornisce macro utilizzabili.

 #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!"); ... 

Se non si desidera la compilazione condizionale, è sufficiente scrivere codice indipendente endian. Ecco un esempio (tratto da Rob Pike ):

Lettura di un intero memorizzato in little-endian su disco, in modo indipendente da endian:

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

Lo stesso codice, cercando di prendere in considerazione la macchina endianness:

 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 

Vedi Endianness – Illustrazione del codice 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; } 

Ecco un’altra versione C. Definisce una macro chiamata wicked_cast() per la wicked_cast() in linea dei tipi tramite letterali C99 union e l’operatore __typeof__ non standard.

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

Se gli interi sono valori a byte singolo, l’endianità non ha senso e verrà generato un errore in fase di compilazione.

Il modo in cui i compilatori C (almeno tutti quelli che conosco) lavorano è che l’endianità deve essere decisa al momento della compilazione. Anche per i processori biendiani (come ARM och MIPS) devi scegliere endianness al momento della compilazione. Inoltre, l’endianità è definita in tutti i formati di file comuni per gli eseguibili (come ELF). Sebbene sia ansible creare un binario binario di codice biandiano (forse per alcuni exploit del server ARM) probabilmente è necessario farlo in assembly.

Stavo esaminando il libro di testo: Computer System: la prospettiva di un programmatore , e c’è un problema nel determinare quale endian è questo dal programma C.

Ho usato la funzione del puntatore per farlo come segue:

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

Come int occupa 4 byte e char occupa solo 1 byte. Potremmo usare un puntatore char per puntare all’int con il valore 1. Quindi se il computer è little endian, il char a cui punta il puntatore char è con valore 1, altrimenti il ​​suo valore dovrebbe essere 0.

Come sottolineato da Coriiander, la maggior parte (se non tutti) di quei codici qui verrà ottimizzata al momento della compilazione, quindi i binari generati non controlleranno “endianità” in fase di esecuzione.

È stato osservato che un dato eseguibile non deve essere eseguito in due ordini di byte diversi, ma non ho idea se questo è sempre il caso, e mi sembra un trucco per me controllare al momento della compilazione. Così ho codificato questa funzione:

 #include  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 non è stato in grado di ottimizzare questo codice, anche se ottimizza gli altri codici qui. Credo che sia perché lascio il valore “casuale” che è stato allocato sulla memoria byte più piccola com’era (almeno 7 dei suoi bit), quindi il compilatore non può sapere quale sia il valore casuale e non ottimizza la funzione di distanza.

Ho anche codificato la funzione in modo che il controllo venga eseguito una sola volta e il valore di ritorno sia memorizzato per i test successivi.