web-dev-qa-db-fra.com

Déterminer l'endianité au moment de la compilation

Existe-t-il un moyen sûr et portable de déterminer (au moment de la compilation) l'endianité de la plate-forme sur laquelle mon programme est compilé? J'écris en C.

[EDIT] Merci pour les réponses, j'ai décidé de m'en tenir à la solution d'exécution!

58
user500944

Ceci est pour la vérification du temps de compilation

Vous pouvez utiliser les informations du fichier d'en-tête boost endian.hpp , qui couvre de nombreuses plates-formes.

modifier pour la vérification de l'exécution

bool isLittleEndian()
{
    short int number = 0x1;
    char *numPtr = (char*)&number;
    return (numPtr[0] == 1);
}

Créez un entier et lisez son premier octet (octet de poids faible). Si cet octet vaut 1, le système est petit endian, sinon c'est big endian.

modifier En y réfléchissant

Oui, vous pourriez rencontrer un problème potentiel sur certaines plates-formes (vous ne pouvez pas penser à aucun) où sizeof(char) == sizeof(short int). Vous pouvez utiliser des types intégraux multi-octets de largeur fixe disponibles dans <stdint.h>, Ou si votre plate-forme ne l'a pas, vous pouvez à nouveau adapter un en-tête boost pour votre usage: stdint.hpp

32
wkl

Pour répondre à la question initiale d'une vérification à la compilation , il n'existe aucun moyen standardisé de le faire qui fonctionnera sur tous les compilateurs existants et futurs, car aucun des normes C, C++ et POSIX existantes définissent des macros pour détecter l'endianité.

Mais, si vous êtes prêt à vous limiter à un ensemble connu de compilateurs, vous pouvez consulter chacune des documentations de ces compilateurs pour savoir quelles macros prédéfinies (le cas échéant) elles utilisent pour définir l'endianité. Cette page répertorie plusieurs macros que vous pouvez rechercher, alors voici un code qui fonctionnerait pour celles-ci:

#if defined(__BYTE_ORDER) && __BYTE_ORDER == __BIG_ENDIAN || \
    defined(__BIG_ENDIAN__) || \
    defined(__ARMEB__) || \
    defined(__THUMBEB__) || \
    defined(__AARCH64EB__) || \
    defined(_MIBSEB) || defined(__MIBSEB) || defined(__MIBSEB__)
// It's a big-endian target architecture
#Elif defined(__BYTE_ORDER) && __BYTE_ORDER == __LITTLE_ENDIAN || \
    defined(__LITTLE_ENDIAN__) || \
    defined(__ARMEL__) || \
    defined(__THUMBEL__) || \
    defined(__AARCH64EL__) || \
    defined(_MIPSEL) || defined(__MIPSEL) || defined(__MIPSEL__)
// It's a little-endian target architecture
#else
#error "I don't know what architecture this is!"
#endif

Si vous ne trouvez pas les macros prédéfinies que votre compilateur utilise dans sa documentation, vous pouvez également essayer de le contraindre à cracher sa liste complète de macros prédéfinies et deviner à partir de là ce qui fonctionnera (recherchez quoi que ce soit avec ENDIAN, ORDER ou le processeur nom de l'architecture). Cette page répertorie un certain nombre de méthodes pour le faire dans différents compilateurs:

Compiler                   C macros                         C++ macros
Clang/LLVM                 clang -dM -E -x c /dev/null      clang++ -dM -E -x c++ /dev/null
GNU GCC/G++                gcc   -dM -E -x c /dev/null      g++     -dM -E -x c++ /dev/null
Hewlett-Packard C/aC++     cc    -dM -E -x c /dev/null      aCC     -dM -E -x c++ /dev/null
IBM XL C/C++               xlc   -qshowmacros -E /dev/null  xlc++   -qshowmacros -E /dev/null
Intel ICC/ICPC             icc   -dM -E -x c /dev/null      icpc    -dM -E -x c++ /dev/null
Microsoft Visual Studio (none)                              (none)
Oracle Solaris Studio      cc    -xdumpmacros -E /dev/null  CC      -xdumpmacros -E /dev/null
Portland Group PGCC/PGCPP  pgcc  -dM -E                     (none)

Enfin, pour terminer, les compilateurs Microsoft Visual C/C++ sont les plus étranges et n'ont aucun des éléments ci-dessus. Heureusement, ils ont documenté leurs macros prédéfinies ici , et vous pouvez utiliser l'architecture du processeur cible pour en déduire l'endianité. Bien que tous les processeurs actuellement pris en charge dans Windows soient de petite taille (_M_IX86, _M_X64, _M_IA64, et _M_ARM sont peu endian), certains processeurs historiquement pris en charge comme le PowerPC (_M_PPC) étaient big-endian. Mais plus important encore, la Xbox 360 est une machine PowerPC big-endian, donc si vous écrivez un en-tête de bibliothèque multiplateforme, cela ne peut pas faire de mal de vérifier _M_PPC.

35
Adam Rosenfield

Avec C99, vous pouvez effectuer la vérification comme:

#define I_AM_LITTLE (((union { unsigned x; unsigned char c; }){1}).c)

Les conditions comme if (I_AM_LITTLE) seront évaluées au moment de la compilation et permettront au compilateur d'optimiser des blocs entiers.

Je n'ai pas la référence pour savoir si c'est à proprement parler un expression constante en C99 (ce qui lui permettrait d'être utilisé dans les initialiseurs pour les données de durée de stockage statique), mais sinon, c'est la prochaine meilleure chose.

16
R..

Lecture intéressante de la FAQ C :

Vous ne pouvez probablement pas. Les techniques habituelles de détection de l'endianité impliquent des pointeurs ou des tableaux de caractères, ou peut-être des unions, mais l'arithmétique du préprocesseur n'utilise que des entiers longs, et il n'y a pas de concept d'adressage. Une autre possibilité tentante est quelque chose comme

  #if 'ABCD' == 0x41424344

mais ce n'est pas fiable non plus.

10
Daniel Băluţă

Je voudrais étendre les réponses pour fournir une fonction constexpr pour C++

union Mix {
    int sdat;
    char cdat[4];
};
static constexpr Mix mix { 0x1 };
constexpr bool isLittleEndian() {
    return mix.cdat[0] == 1;
}

Puisque mix est aussi constexpr, c'est le temps de compilation et peut être utilisé dans constexpr bool isLittleEndian(). Doit être sûr à utiliser.

Mise à jour

Comme @Cheersandhth l'a souligné ci-dessous, cela semble être problématique.

La raison en est qu'il est non conforme à C++ 11-Standard, où type punning est interdit. Il ne peut toujours y avoir qu'un seul membre du syndicat actif à la fois. Avec un compilateur conforme standard, vous obtiendrez une erreur.

Donc, ne l'utilisez pas en C++. Il semble que vous puissiez le faire en C cependant. Je laisse ma réponse à des fins éducatives :-) et parce que la question concerne C ...

Update 2

Cela suppose que int a la taille de 4 chars, ce qui n'est pas toujours indiqué comme @ PetrVepřek l'a correctement indiqué ci-dessous. Pour rendre votre code vraiment portable, vous devez être plus intelligent ici. Cela devrait cependant suffire dans de nombreux cas. Notez que sizeof(char) est toujours 1, Par définition. Le code ci-dessus suppose sizeof(int)==4.

7
towi

Pas pendant la compilation, mais peut-être pendant l'exécution. Voici une fonction C que j'ai écrite pour déterminer l'endianité:

/*  Returns 1 if LITTLE-ENDIAN or 0 if BIG-ENDIAN  */
#include <inttypes.h>
int endianness()
{
  union { uint8_t c[4]; uint32_t i; } data;
  data.i = 0x12345678;
  return (data.c[0] == 0x78);
}
6
pr1268

De Enfin, détection d'endianité sur une ligne dans le préprocesseur C :

#include <stdint.h>

#define IS_BIG_ENDIAN (*(uint16_t *)"\0\xff" < 0x100)

Tout optimiseur décent résoudra cela au moment de la compilation. gcc fait à -O1.

Bien sûr stdint.h est C99. Pour la portabilité ANSI/C89, voir la bibliothèque de Doug Gwyn Instant C9x .

3
user2975337

J'ai utilisé une fois une construction comme celle-ci:

uint16_t  HI_BYTE  = 0,
          LO_BYTE  = 1;
uint16_t  s = 1;

if(*(uint8_t *) &s == 1) {   
   HI_BYTE = 1;
   LO_BYTE = 0;
} 

pByte[HI_BYTE] = 0x10;
pByte[LO_BYTE] = 0x20;

gcc avec -O2 a été en mesure de rendre complètement le temps de compilation. Cela signifie que les variables HI_BYTE Et LO_BYTE Ont été entièrement remplacées et même l'accès pByte a été remplacé dans l'assembleur par l'équivalent de *(unit16_t *pByte) = 0x1020;.

C'est autant de temps de compilation que possible.

1
Patrick Schlüter

À ma connaissance non, pas pendant la compilation.

Au moment de l'exécution, vous pouvez effectuer des vérifications triviales telles que la définition d'une valeur multi-octets sur une chaîne de bits connue et inspecter les octets qui en résultent. Par exemple, en utilisant une union,

typedef union {
    uint32_t Word;
    uint8_t bytes[4];
} byte_check;

ou casting,

uint32_t Word;
uint8_t * bytes = &Word;

Veuillez noter que pour les contrôles d'endianité entièrement portables, vous devez prendre en compte les systèmes big-endian, little-endian et mixed-endian.

0
Christoffer

Utilisez CMake TestBigEndian as

INCLUDE(TestBigEndian)
TEST_BIG_ENDIAN(ENDIAN)
IF (ENDIAN)
    # big endian
ELSE (ENDIAN)
    # little endian
ENDIF (ENDIAN)
0
Like

EDIT2: Cette méthode ne fonctionne pas. La représentation de la constante multi-octets est spécifique au compilateur/plateforme et ne peut pas être utilisée de manière fiable. Le lien fourni par interjay ( http://www.ideone.com/LaKpj ) donne un exemple où il échoue. Sur Solaris/SPARC, le même compilateur gcc 4.3.3 donne la bonne réponse, mais le compilateur SUNStudio 12 aura le même comportement que le gcc 4.3.4 sur x86 utilisé sur ce lien.

Donc, nous pouvons conclure, toujours pas une bonne utilisation du caractère multi-octets

Trouvé cette nouvelle méthode qui a l'avantage d'être simple et de compiler le temps.

switch('AB') {
  case 0x4142: printf("ASCII  Big endian\n"); break;
  case 0x4241: printf("ASCII  Little endian\n"); break;
  case 0xC1C2: printf("EBCDIC Big endian\n"); break;
  case 0xC2C1: printf("EBCDIC Little endian\n"); break;
}

ÉDITER:

Trouvé même un moyen de le faire dans le pré-processeur:

#if 'AB' == 0x4142
#error "ASCII  Big endian\n"
#Elif 'AB' == 0x4241
#error "ASCII  Little endian\n"
#Elif 'AB' == 0xC1C2
#error "EBCDIC Big endian\n"
#Elif 'AB' == 0xC2C1
#error "EBCDIC Little endian\n"
#else
#error "unknown coding and endianness\n"
#endif

Et avant que quelqu'un ne demande, les constantes de caractères multi-octets sont ANSI-C (même C90) mais sont définies par l'implémentation. Voici la première application utile que j'ai trouvée pour eux.

0
Patrick Schlüter