C Définition de macro pour déterminer la machine big endian ou little endian?

107

Existe-t-il une définition de macro sur une ligne pour déterminer l'endianité de la machine. J'utilise le code suivant mais le convertir en macro serait trop long.

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

    return (test_endian[0] == 0);
}
manav mn
la source
2
Pourquoi ne pas inclure le même code dans une macro?
Sharptooth
4
Vous ne pouvez pas déterminer l'endianness de manière portable avec le préprocesseur C seul. Vous voulez également 0au lieu de NULLdans votre test final, et changer l'un des test_endianobjets en autre chose :-).
Alok Singhal
2
Aussi pourquoi une macro est-elle nécessaire? La fonction en ligne ferait la même chose et est beaucoup plus sûre.
sharptooth
13
@Sharptooth, une macro est attrayante car sa valeur peut être connue au moment de la compilation, ce qui signifie que vous pouvez utiliser l'endianness de votre plate-forme pour contrôler l'instanciation du modèle, par exemple, ou peut-être même sélectionner différents blocs de code avec une #ifdirective.
Rob Kennedy
3
C'est vrai, mais inefficace. Si j'ai un processeur little-endian et que j'écris des données little-endian sur le fil ou dans un fichier, je préfère éviter de décompresser et de reconditionner les données sans aucun but. J'écrivais des pilotes vidéo pour gagner ma vie. Lors de l'écriture de pixels sur une carte vidéo, il est extrêmement important d'optimiser chaque emplacement possible.
Edward Falk

Réponses:

102

Code prenant en charge les ordres d'octets arbitraires, prêt à être placé dans un fichier appelé 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

Vous vérifieriez les systèmes little endian via

O32_HOST_ORDER == O32_LITTLE_ENDIAN
Christoph
la source
11
Cela ne vous permet pas de décider de l' endian-ness avant l'exécution. La compilation suivante échoue car. / ** isLittleEndian :: result -> 0 ou 1 * / struct isLittleEndian {énumération isLittleEndianResult {result = (O32_HOST_ORDER == O32_LITTLE_ENDIAN)}; };
user48956
3
Est-il impossible d'obtenir le résultat avant l'exécution?
k06a du
8
Pourquoi char? Meilleure utilisation uint8_tet échec si ce type n'est pas disponible (ce qui peut être vérifié par #if UINT8_MAX). Notez que CHAR_BITc'est indépendant de uint8_t.
Andreas Spindler
2
C'est UB en c ++: stackoverflow.com/questions/11373203/…
Lyberta
3
Permettez-moi d'en ajouter un autre dans le mélange, pour être complet:O32_HONEYWELL_ENDIAN = 0x02030001ul /* Honeywell 316 */
Edward Falk
49

Si vous avez un compilateur qui prend en charge les littéraux composés C99:

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

ou:

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

En général cependant, vous devriez essayer d'écrire du code qui ne dépend pas de l'endianness de la plate-forme hôte.


Exemple d'implémentation indépendante de l'hôte-endianness 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];
}
caf
la source
3
"vous devriez essayer d'écrire du code qui ne dépend pas de l'endianness de la plate-forme hôte". Malheureusement, mon plaidoyer, "Je sais que nous écrivons une couche de compatibilité POSIX, mais je ne veux pas implémenter ntoh, car cela dépend de l'endianness de la plate-forme hôte" est toujours tombé dans l'oreille d'un sourd ;-). La gestion du format graphique et le code de conversion est l'autre candidat principal que j'ai vu - vous ne voulez pas tout baser sur l'appel de ntohl tout le temps.
Steve Jessop
5
Vous pouvez implémenter ntohld'une manière qui ne dépend pas de l'endianness de la plate-forme hôte.
caf
1
@caf comment écririez-vous ntohl d'une manière indépendante de l'hôte-endianness?
Hayri Uğur Koltuk
3
@AliVeli: J'ai ajouté un exemple d'implémentation à la réponse.
caf
6
Je devrais également ajouter pour mémoire, que "(* (uint16_t *)" \ 0 \ xff "<0x100)" ne se compilera pas en une constante, peu importe combien j'optimise, du moins avec gcc 4.5.2. Il crée toujours du code exécutable.
Edward Falk du
43

Il n'y a pas de norme, mais sur de nombreux systèmes, <endian.h>cela vous donnera quelques définitions à rechercher.

Ignacio Vazquez-Abrams
la source
30
Testez l'endianness avec #if __BYTE_ORDER == __LITTLE_ENDIANet #elif __BYTE_ORDER == __BIG_ENDIAN. Et générer un #errorautre.
To1ne
6
<endian.h>n'est pas disponible sur Windows
rustyx
2
Les projets Android et Chromium utilisent endian.hsauf si __APPLE__ou _WIN32est défini.
patryk.beza
1
Dans OpenBSD 6.3, <endian.h> fournit #if BYTE_ORDER == LITTLE_ENDIAN(ou BIG_ENDIAN) sans traits de soulignement avant les noms. _BYTE_ORDERest uniquement pour les en-têtes système. __BYTE_ORDERn'existe pas.
George Koehler
@ To1ne Je doute que Endianness soit pertinent pour Windows, car Windows (du moins actuellement) ne fonctionne que sur les machines x86 et ARM. x86 étant toujours LE et ARM configurable pour utiliser l'une ou l'autre architecture.
SimonC
27

Pour détecter l'endianness au moment de l'exécution, vous devez pouvoir vous référer à la mémoire. Si vous vous en tenez au standard C, la déclaration d'une variable en mémoire nécessite une instruction, mais le retour d'une valeur nécessite une expression. Je ne sais pas comment faire cela dans une seule macro - c'est pourquoi gcc a des extensions :-)

Si vous souhaitez avoir un fichier .h, vous pouvez définir

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

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

puis vous pouvez utiliser la ENDIANNESSmacro comme vous le souhaitez.

Norman Ramsey
la source
6
J'aime cela parce que cela reconnaît l'existence d'une endianité autre que petite et grande.
Alok Singhal
6
En parlant de cela, il peut être intéressant d'appeler la macro INT_ENDIANNESS, ou même UINT32_T_ENDIANNESS, car elle ne teste que la représentation de stockage d'un type. Il y a un ARM ABI où les types intégraux sont petit-boutien, mais les doubles sont le moyen-boutien (chaque mot est petit-boutien, mais le mot avec le bit de signe précède l'autre mot). Cela a provoqué une certaine excitation au sein de l'équipe du compilateur pendant environ un jour, je peux vous le dire.
Steve Jessop
19

Si vous voulez vous fier uniquement au préprocesseur, vous devez trouver la liste des symboles prédéfinis. L'arithmétique du préprocesseur n'a pas de concept d'adressage.

GCC sur Mac définit __LITTLE_ENDIAN__ou__BIG_ENDIAN__

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

Ensuite, vous pouvez ajouter plus de directives conditionnelles de préprocesseur basées sur la détection de plate-forme comme #ifdef _WIN32etc.

Gregory Pakosz
la source
6
GCC 4.1.2 sous Linux ne semble pas définir ces macros, bien que GCC 4.0.1 et 4.2.1 les définissent sur Macintosh. Ce n'est donc pas une méthode fiable pour le développement multiplateforme, même si vous êtes autorisé à dicter le compilateur à utiliser.
Rob Kennedy
1
oh ouais c'est parce qu'il n'est défini que par GCC sur Mac.
Gregory Pakosz
Remarque: My GCC (sur Mac) définit #define __BIG_ENDIAN__ 1et #define _BIG_ENDIAN 1.
clang 5.0.1 pour OpenBSD / amd64 a #define __LITTLE_ENDIAN__ 1. Cette macro semble être une fonction clang, pas une fonction gcc. La gcccommande sur certains Mac n'est pas gcc, elle résonne.
George Koehler
GCC 4.2.1 sur Mac était GCC à l'époque
Gregory Pakosz
15

Je crois que c'est ce qui a été demandé. Je n'ai testé cela que sur une petite machine endian sous msvc. Quelqu'un peut confirmer sur une machine 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

En remarque (spécifique au compilateur), avec un compilateur agressif, vous pouvez utiliser l'optimisation "d'élimination de code mort" pour obtenir le même effet qu'un temps de compilation #ifcomme ceci:

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

Ce qui précède repose sur le fait que le compilateur reconnaît les valeurs constantes au moment de la compilation, supprime entièrement le code à l'intérieur if (false) { ... }et remplace le code comme if (true) { foo(); }avec foo();Le pire des cas: le compilateur ne fait pas l'optimisation, vous obtenez toujours du code correct mais un peu plus lent.

ggpp23
la source
J'aime cette méthode, mais corrigez-moi si je me trompe: cela ne fonctionne que lorsque vous compilez sur la machine pour laquelle vous construisez, n'est-ce pas?
leetNightshade
3
gcc renvoie également une erreur due à des constantes de caractères à plusieurs caractères. Ainsi, pas portable.
Edward Falk
2
quel compilateur vous permet d'écrire 'ABCD'?
Ryan Haining
2
De nombreux compilateurs autoriseront les constantes de caractères multi-octets dans des modes de conformité assouplis, mais exécutent la partie supérieure avec clang -Wpedantic -Werror -Wall -ansi foo.cet il y aura une erreur. (Clang et ce en particulier: -Wfour-char-constants -Werror)
@Edward Falk Ce n'est pas une erreur d'avoir une constante à plusieurs caractères dans le code. Il s'agit du comportement défini par l'implémentation C11 6.4.4.4. 10. gcc et d'autres peuvent / peuvent ne pas avertir / erreur selon les paramètres, mais ce n'est pas une erreur C. Il n'est certainement pas courant d'utiliser des constantes de caractères à plusieurs caractères.
chux - Réintégrer Monica
10

Si vous recherchez un test de compilation et que vous utilisez gcc, vous pouvez faire:

#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__

Consultez la documentation de gcc pour plus d'informations.

Jérôme Pouiller
la source
3
C'est certainement la meilleure réponse pour tous ceux qui utilisent gcc
rtpax
2
__BYTE_ORDER__est disponible depuis GCC 4.6
Benoit Blanchon
8

Vous pouvez en effet accéder à la mémoire d'un objet temporaire en utilisant un littéral composé (C99):

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

Quel GCC évaluera au moment de la compilation.

u0b34a0f6ae
la source
Je l'aime. Existe-t-il un moyen portable, au moment de la compilation, de savoir que vous compilez sous C99?
Edward Falk
1
Oh, et si ce n'est pas GCC?
Edward Falk
1
@EdwardFalk Oui. #if __STDC_VERSION__ >= 199901L.
Jens
7

La 'bibliothèque réseau C' offre des fonctions pour gérer l'endian'ness. A savoir htons (), htonl (), ntohs () et ntohl () ... où n est "network" (ie. Big-endian) et h est "host" (ie. Endian'ness de la machine exécutant le code).

Ces «fonctions» apparentes sont (généralement) définies comme des macros [voir <netinet / in.h>], il n'y a donc pas de surcharge d'exécution pour les utiliser.

Les macros suivantes utilisent ces 'fonctions' pour évaluer l'endian'ness.

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

En outre:

La seule fois où j'ai besoin de connaître le caractère endian d'un système, c'est lorsque j'écris une variable [dans un fichier / autre] qui peut être lue par un autre système dont le caractère endian est inconnu (pour une compatibilité multiplateforme ) ... Dans de tels cas, vous préférerez peut-être utiliser directement les fonctions 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);
Bateau bleu
la source
Cela ne répond pas vraiment à la question qui cherchait un moyen rapide de déterminer l'endianité.
Oren
@Oren: En ce qui concerne votre critique valable, j'ai ajouté des détails qui répondent plus directement à la question initiale.
BlueChip
6

Utilisez une fonction en ligne plutôt qu'une macro. De plus, vous devez stocker quelque chose en mémoire, ce qui est un effet secondaire pas très agréable d'une macro.

Vous pouvez le convertir en une macro courte en utilisant une variable statique ou globale, comme ceci:

static int s_endianess = 0;
#define ENDIANESS() ((s_endianess = 1), (*(unsigned char*) &s_endianess) == 0)
utilisateur231967
la source
je pense que c'est le meilleur car c'est le plus simple. Cependant, il ne teste pas contre l'endian mixte
Hayri Uğur Koltuk
1
Pourquoi pas s_endianess défini sur 1 pour commencer?
SquareRootOfTwentyThree
5

Bien qu'il n'y ait pas de #define portable ou quelque chose sur lequel s'appuyer, les plates-formes fournissent des fonctions standard pour la conversion vers et depuis votre endian «hôte».

En règle générale, vous effectuez le stockage - sur disque ou réseau - en utilisant 'network endian', ce qui est BIG endian, et le calcul local en utilisant host endian (qui sur x86 est LITTLE endian). Vous utilisez htons()et ntohs()et vos amis pour convertir entre les deux.

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

la source
6
Cela génère également du code exécutable, pas une constante. Vous ne pouvez pas faire "#if IS_BIG_ENDIAN"
Edward Falk
J'aime cette solution car elle ne repose pas sur un comportement non défini des normes C / C ++, pour autant que je sache. Ce n'est pas le moment de la compilation, mais la seule solution standard pour cela est d'attendre c ++ 20 std :: endian
ceztko
4

N'oubliez pas que l'endianness n'est pas toute l'histoire - la taille de charpeut ne pas être de 8 bits (par exemple DSP), la négation du complément à deux n'est pas garantie (par exemple Cray), un alignement strict peut être requis (par exemple, SPARC, ARM passe également au milieu -endian lorsqu'il pas aligné), etc., etc.

Il pourrait être préférable de cibler une architecture de processeur spécifique place.

Par exemple:

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

Notez que cette solution n'est malheureusement pas non plus ultra-portable, car elle dépend de définitions spécifiques au compilateur (il n'y a pas de standard, mais voici une belle compilation de ces définitions).

rustyx
la source
3

Essaye ça:

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

   TEST;
}
Prasoon Saurav
la source
2

Veuillez faire attention que la plupart des réponses ici ne sont pas portables, car les compilateurs aujourd'hui évalueront ces réponses au moment de la compilation (dépend de l'optimisation) et retourneront une valeur spécifique basée sur une endianité spécifique, alors que l'endianité réelle de la machine peut différer. Les valeurs sur lesquelles l'endianité est testée n'atteindront jamais la mémoire système, donc le code réel exécuté renverra le même résultat quelle que soit l'endianité réelle.

Par exemple , dans ARM Cortex-M3, l'endianness implémentée se reflétera dans un bit d'état AIRCR.ENDIANNESS et le compilateur ne peut pas connaître cette valeur au moment de la compilation.

Sortie de compilation pour certaines des réponses suggérées ici:

https://godbolt.org/z/GJGNE2 pour cette réponse,

https://godbolt.org/z/Yv-pyJ pour cela réponse, et ainsi de suite.

Pour le résoudre, vous devrez utiliser le volatilequalificatif. Yogeesh H T« s réponse est le plus proche de l' utilisation réelle de la vie d'aujourd'hui, mais comme Christophsuggère une solution plus complète, une légère correction à sa réponse serait la réponse complète, il suffit d' ajouter volatileà la déclaration syndicale: static const volatile union.

Cela assurerait le stockage et la lecture à partir de la mémoire, ce qui est nécessaire pour déterminer l'endianité.

user2162550
la source
2

Si vous videz le préprocesseur #defines

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

Vous pouvez généralement trouver des choses qui vous aideront. Avec une logique de compilation.

#define __LITTLE_ENDIAN__ 1
#define __BYTE_ORDER__ __ORDER_LITTLE_ENDIAN__

Cependant, divers compilateurs peuvent avoir des définitions différentes.

Sam P
la source
0

Ma réponse n'est pas celle demandée mais il est vraiment simple de trouver si votre système est petit ou gros boutien?

Code:

#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
la source
0

C Code pour vérifier si un système est petit-boutiste ou grand-indien.

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
la source
-3

Macro pour trouver des endiannes

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

ou

#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
la source
3
La première macro est incorrecte et retournera toujours "Big-Endian". Le décalage de bits n'est pas affecté par l'endianness - l'endianness n'affecte que les lectures et les stockages dans la mémoire.
GaspardP