web-dev-qa-db-fra.com

Détection de l’endianisme par programme dans un programme C++

Existe-t-il un moyen programmatique de détecter si vous êtes ou non sur une architecture big-endian ou little-endian? Je dois être capable d'écrire du code qui s'exécutera sur un système Intel ou PPC et d'utiliser exactement le même code (c'est-à-dire sans compilation conditionnelle).

183
Jay T

Je n'aime pas la méthode basée sur le typage - elle sera souvent mise en garde par le compilateur. C'est exactement ce que les syndicats sont pour!

bool is_big_endian(void)
{
    union {
        uint32_t i;
        char c[4];
    } bint = {0x01020304};

    return bint.c[0] == 1; 
}

Le principe est équivalent au type de cas suggéré par d'autres, mais cela est plus clair - et, selon C99, il est garanti que tout est correct. gcc préfère cela par rapport à la distribution directe du pointeur.

C’est aussi bien mieux que de fixer l’endianité au moment de la compilation - pour les OS supportant la multi-architecture (fat binary sur Mac os x par exemple), cela fonctionnera aussi bien pour ppc/i386, alors qu’il est très facile de tout gâcher .

161
David Cournapeau

Vous pouvez le faire en définissant un int et en masquant des bits, mais le moyen le plus simple consiste probablement simplement à utiliser les opérations de conversion d'octets réseau intégrées (l'ordre des octets du réseau étant toujours le gros endian).

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

Un peu bidouiller pourrait être plus rapide, mais cette façon est simple, directe et assez impossible à gâcher.

77
Eric Petroelje

S'il vous plaît voir cet article :

Voici un code pour déterminer ce qui est le type de votre machine

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

Cela se fait normalement au moment de la compilation (spécialement pour des raisons de performances) en utilisant les fichiers d’en-tête disponibles à partir du compilateur ou en créant vos propres Sous Linux, vous avez le fichier d'en-tête "/usr/include/endian.h"

32
bill

Vous pouvez utiliser std::endian si vous avez accès à un compilateur C++ 20 tel que GCC 8+ ou Clang 7+:

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

Euh ... Cela me surprend que personne ne se soit rendu compte que le compilateur va simplement optimiser le test et mettre un résultat fixe comme valeur de retour. Cela rend tous les exemples de code ci-dessus, effectivement inutiles. La seule chose qui serait retournée est la finalité au moment de la compilation! Et oui, j'ai testé tous les exemples ci-dessus. Voici un exemple avec MSVC 9.0 (Visual Studio 2008).

Code 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;
}

Démontage

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

Peut-être est-il possible de désactiver TOUT optimisation au moment de la compilation pour cette fonction uniquement, mais je ne le sais pas. Sinon, il est peut-être possible de le coder en dur dans Assembly, bien que ce ne soit pas portable. Et même dans ce cas, cela pourrait être optimisé. Cela me fait penser qu'il me faut un assembleur vraiment nul, implémenter le même code pour tous les processeurs/jeux d'instructions existants, et bien .... tant pis.

En outre, quelqu'un ici a dit que l'endianité ne change pas pendant l'exécution. FAUX. Il existe des machines bi-endiennes. Leur endianité peut varier en fonction de l'exécution. AUSSI, il n’ya pas que le petit Endian et le Big Endian, mais aussi d’autres endiannesses (quel mot).

Je déteste et aime coder en même temps ...

15
Coriiander

Je suis surpris que personne n'ait mentionné les macros que le pré-processeur définit par défaut. Bien que ceux-ci varient en fonction de votre plate-forme; ils sont beaucoup plus propres que d'avoir à écrire votre propre endian-check. 

Par exemple; si nous regardons les macros intégrées que GCC définit (sur une machine X86-64):

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

Sur un PPC ordinateur, je reçois:

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

(La magie :| gcc -dM -E -x c - imprime toutes les macros intégrées).

14
DaveR

Déclarez une variable int:

int variable = 0xFF;

Maintenant, utilisez les pointeurs sur différentes parties et vérifiez ce qu’ils contiennent.

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

Selon lequel pointe sur l'octet 0xFF, vous pouvez maintenant détecter l'endianité. Cela nécessite sizeof (int)> sizeof (char), mais c'est certainement vrai pour les plates-formes discutées.

14
sharptooth

Pour plus de détails, vous pouvez consulter cet article codeproject Concepts de base sur Endianness :

Comment tester dynamiquement le type Endian au moment de l'exécution?

Comme expliqué dans Computer FAQ Animation, vous pouvez utiliser le fonction suivante pour voir si votre code fonctionne sur un petit ou grand endian système: 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);
}

Ce code attribue la valeur 0001h à un Entier de 16 bits. Un pointeur de caractère est alors affecté au point à la première octet (le moins significatif) de valeur entière. Si le premier octet de l'entier est 0x01h, alors le système est Little-Endian (0x01h est dans l'adresse la plus basse ou la moins significative, ). Si c'est 0x00h, alors le le système est Big-Endian.

7
none

La méthode C++ a consisté à utiliser boost , où les vérifications du préprocesseur et les conversions sont compartimentées dans des bibliothèques très soigneusement testées.

La bibliothèque Predef (boost/predef.h) reconnaît quatre types différents d’endianness .

La bibliothèque Endian devait être soumise à la norme C++, et prend en charge une grande variété d’opérations sur des données sensibles à Endian.

Comme indiqué dans les réponses ci-dessus, Endianness fera partie de c ++ 20.

6
fuzzyTew

Sauf si vous utilisez un framework qui a été porté sur PPC et des processeurs Intel, vous devrez effectuer des compilations conditionnelles car les plates-formes PPC et Intel ont des architectures matérielles, des pipelines, des bus, etc. complètement différents. Cela rend le code d'assemblage complètement différent entre les deux.

Pour trouver l’endianisme, procédez comme suit:

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

Vous obtiendrez soit tempChar soit 0x12 ou 0x34, à partir duquel vous connaîtrez le caractère endianiste.

5
samoz

Comme indiqué ci-dessus, utilisez des astuces de syndicat.

Les problèmes indiqués ci-dessus posent cependant peu de problèmes, notamment le fait que l'accès à la mémoire non alignée est notoirement lent pour la plupart des architectures et que certains compilateurs ne reconnaissent même pas de tels prédicats constants, à moins que Word ne soit aligné.

Parce que le simple test endian est ennuyeux, voici la fonction (modèle) qui inversera l’entrée/la sortie d’un nombre entier arbitraire en fonction de vos spécifications, quelle que soit l’architecture de l’hôte.

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

Usage:

Pour convertir d'endian donné en hôte, utilisez:

Host = endian(source, endian_of_source)

Pour convertir un hôte endian en un utilisateur donné, utilisez:

output = endian(hostsource, endian_you_want_to_output)

Le code qui en résulte est aussi rapide que l’écriture main Assembly sur clang, sur gcc c’est un peu plus lent (déroulé &, <<, >>, | pour chaque octet) mais toujours correct.

5
kat
bool isBigEndian()
{
    static const uint16_t m_endianCheck(0x00ff);
    return ( *((uint8_t*)&m_endianCheck) == 0x0); 
}
4
Paolo Brandoli

temps de compilation, non-macro, solution C++ 11 constexpr:

union {
  uint16_t s;
  unsigned char c[2];
} constexpr static  d {1};

constexpr bool is_little_endian() {
  return d.c[0] == 1;
}
4
zhaorufei

Je ferais quelque chose comme ça:

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

Dans le même ordre d'idées, vous obtiendrez une fonction efficace qui effectue le calcul une seule fois. 

4
Jeremy Mayhew

non testé, mais dans mon esprit, cela devrait fonctionner? parce que ce sera 0x01 sur le petit endian, et 0x00 sur le gros endian?

bool runtimeIsLittleEndian(void)
{
 volatile uint16_t i=1;
 return  ((uint8_t*)&i)[0]==0x01;//0x01=little, 0x00=big
}
3
hanshenrik
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");

Ceci est une autre solution. Similaire à la solution d'Andrew Hare.

3
Neeraj

Vous pouvez également le faire via le préprocesseur en utilisant quelque chose comme un fichier d’en-tête boost qui peut être trouvé boost endian

2
nmushell

À moins que l'en-tête endian soit uniquement GCC, il fournit des macros que vous pouvez utiliser.

#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!");
...
1
Mark A. Libby

Si vous ne voulez pas de compilation conditionnelle, vous pouvez simplement écrire du code indépendant de Endian. Voici un exemple (tiré de Rob Pike ):

Lecture d'un entier stocké dans le disque little-endian sur le disque, de manière indépendante de l'endian:

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

Le même code, essayant de prendre en compte l’endianité de la machine:

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
1
fjardon
int i=1;
char *c=(char*)&i;
bool littleendian=c;
1
Jon Bright

Que dis-tu de ça?

#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;
}
1
Abhay

La façon dont les compilateurs C (du moins tout le monde que je connaisse) travaille dans l’endianisme a à décider au moment de la compilation. Même pour les processeurs biendiens (comme ARM ou MIPS), vous devez choisir l’endianité au moment de la compilation. De plus, l’endianité est définie dans tous les formats de fichiers courants des exécutables (tels que ELF). Bien qu'il soit possible de créer un blob binaire de code biandien (pour un exploit de serveur ARM peut-être?), Cela doit probablement être fait dans Assembly.

0
Fabel

Voir Endianness - Illustration du code de niveau 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;
} 
0
gimel

N'utilisez pas de union!

C++ n'autorise pas le typage via unions!
La lecture d'un champ d'union qui n'était pas le dernier champ écrit est comportement non défini !
De nombreux compilateurs acceptent de le faire en tant qu’extensions, mais le langage ne donne aucune garantie.

Voir cette réponse pour plus de détails:

https://stackoverflow.com/a/11996970


Il n'y a que deux réponses valides qui sont garanties d'être portables.

La première réponse, si vous avez accès à un système prenant en charge C++ 20,
doit utiliser std::endian à partir de l'en-tête <type_traits>.

(Au moment de la rédaction de ce document, C++ 20 n’a pas encore été publié, mais à moins que quelque chose affecte l’inclusion de std::endian, il s’agit du moyen privilégié pour tester l’endianité lors de la compilation à partir de C++ 20.)

C++ 20 à partir de

constexpr bool is_little_endian = (std::endian::native == std::endian::little);

Avant C++ 20, la seule solution valable était de stocker un entier, puis d'inspecter son premier octet par le biais d'une frappe de type.
Contrairement à l'utilisation de unions, cela est expressément autorisé par le système de types de C++.

Il est également important de se rappeler que, pour une portabilité optimale, static_cast doit être utilisé,
car reinterpret_cast est défini par l'implémentation.

Si un programme tente d'accéder à la valeur stockée d'un objet via une valeur glval autre que l'un des types suivants, le comportement est indéfini: ... un type char ou unsigned char.

C++ 11 à partir de

enum class endianness
{
    little = 0,
    big = 1,
};

inline endianness get_system_endianness()
{
    const int value { 0x01 };
    const void * address = static_cast<const void *>(&value);
    const unsigned char * least_significant_address = static_cast<const unsigned char *>(address);
    return (*least_significant_address == 0x01) ? endianness::little : endianness::big;
}

C++ 11 et suivants (sans enum)

inline bool is_system_little_endian()
{
    const int value { 0x01 };
    const void * address = static_cast<const void *>(&value);
    const unsigned char * least_significant_address = static_cast<const unsigned char *>(address);
    return (*least_significant_address == 0x01);
}

C++ 98/C++ 03

inline bool is_system_little_endian()
{
    const int value = 0x01;
    const void * address = static_cast<const void *>(&value);
    const unsigned char * least_significant_address = static_cast<const unsigned char *>(address);
    return (*least_significant_address == 0x01);
}
0
Pharap

Voici une autre version C. Il définit une macro appelée wicked_cast() pour la frappe de type en ligne via les littéraux d'union C99 et l'opérateur non-standard __typeof__.

#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 les entiers sont des valeurs à un octet, l'endianisme n'a pas de sens et une erreur lors de la compilation sera générée.

0
Christoph

bien qu’il n’existe aucun moyen rapide et standard de le déterminer, cela l’affiche:

#include <stdio.h> 
int main()  
{ 
   unsigned int i = 1; 
   char *c = (char*)&i; 
   if (*c)     
       printf("Little endian"); 
   else
       printf("Big endian"); 
   getchar(); 
   return 0; 
} 
0
yekanchi