C Makrodefinition zur Bestimmung der Big-Endian- oder Little-Endian-Maschine?

107

Gibt es eine einzeilige Makrodefinition, um die Endianness der Maschine zu bestimmen? Ich verwende den folgenden Code, aber die Konvertierung in ein Makro wäre zu lang.

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

    return (test_endian[0] == 0);
}
manav mn
quelle
2
Warum nicht denselben Code in ein Makro aufnehmen?
Scharfzahn
4
Mit dem C-Präprozessor können Sie die Endianness nicht allein bestimmen. Sie möchten auch 0statt NULLin Ihrem letzten Test eines der test_endianObjekte in etwas anderes ändern :-).
Alok Singhal
2
Auch warum ist ein Makro notwendig? Die Inline-Funktion würde dasselbe tun und ist viel sicherer.
Scharfzahn
13
@Sharptooth, ein Makro ist ansprechend, da sein Wert zum Zeitpunkt der Kompilierung möglicherweise bekannt ist. Dies bedeutet, dass Sie die Endianität Ihrer Plattform beispielsweise zur Steuerung der Vorlageninstanziierung verwenden oder sogar verschiedene Codeblöcke mit einer #ifDirektive auswählen können .
Rob Kennedy
3
Das stimmt, ist aber ineffizient. Wenn ich eine Little-Endian-CPU habe und Little-Endian-Daten in den Draht oder in eine Datei schreibe, würde ich es lieber vermeiden, Daten ohne Zweck zu entpacken und neu zu packen. Ich habe meinen Lebensunterhalt mit Videotreibern verdient. Beim Schreiben von Pixeln auf eine Grafikkarte ist es äußerst wichtig, jeden Ort zu optimieren, den Sie können.
Edward Falk

Antworten:

102

Code, der beliebige Bytereihenfolgen unterstützt und bereit ist, in eine Datei mit dem Namen 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

Sie würden über nach kleinen Endian-Systemen suchen

O32_HOST_ORDER == O32_LITTLE_ENDIAN
Christoph
quelle
11
Auf diese Weise können Sie jedoch erst zur Laufzeit über Endianität entscheiden . Folgendes kann nicht kompiliert werden, weil. / ** isLittleEndian :: result -> 0 oder 1 * / struct isLittleEndian {enum isLittleEndianResult {result = (O32_HOST_ORDER == O32_LITTLE_ENDIAN)}; };
user48956
3
Ist es unmöglich, das Ergebnis bis zur Laufzeit zu erhalten?
k06a
8
Warum char? Besser verwenden uint8_tund fehlschlagen, wenn dieser Typ nicht verfügbar ist (was von überprüft werden kann #if UINT8_MAX). Beachten Sie, dass CHAR_BITunabhängig von uint8_t.
Andreas Spindler
2
Dies ist UB in c ++: stackoverflow.com/questions/11373203/…
Lyberta
3
Lassen Sie mich der Vollständigkeit halber noch einen in die Mischung werfen:O32_HONEYWELL_ENDIAN = 0x02030001ul /* Honeywell 316 */
Edward Falk
49

Wenn Sie einen Compiler haben, der zusammengesetzte C99-Literale unterstützt:

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

oder:

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

Im Allgemeinen sollten Sie jedoch versuchen, Code zu schreiben, der nicht von der Endlichkeit der Host-Plattform abhängt.


Beispiel für eine vom Host-Endianness unabhängige Implementierung von 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
quelle
3
"Sie sollten versuchen, Code zu schreiben, der nicht von der Endlichkeit der Host-Plattform abhängt." Leider stieß mein Plädoyer "Ich weiß, dass wir eine POSIX-Kompatibilitätsschicht schreiben, aber ich möchte ntoh nicht implementieren, da es von der Endianität der Host-Plattform abhängt" immer auf taube Ohren ;-). Der Umgang mit Grafikformaten und der Konvertierungscode sind der andere Hauptkandidat, den ich gesehen habe - Sie möchten nicht immer alles auf ntohl stützen.
Steve Jessop
5
Sie können ntohlauf eine Weise implementieren , die nicht von der Endlichkeit der Host-Plattform abhängt.
Café
1
@caf wie würdest du ntohl host-endianness-unabhängig schreiben?
Hayri Uğur Koltuk
3
@AliVeli: Ich habe der Antwort eine Beispielimplementierung hinzugefügt.
Café
6
Ich sollte auch für den Datensatz hinzufügen, dass "(* (uint16_t *)" \ 0 \ xff "<0x100)" nicht zu einer Konstanten kompiliert wird, egal wie viel ich optimiere, zumindest mit gcc 4.5.2. Es wird immer ausführbarer Code erstellt.
Edward Falk
43

Es gibt keinen Standard, aber auf vielen Systemen <endian.h>gibt es einige Definitionen, nach denen Sie suchen müssen.

Ignacio Vazquez-Abrams
quelle
30
Testen Sie die Endianness mit #if __BYTE_ORDER == __LITTLE_ENDIANund #elif __BYTE_ORDER == __BIG_ENDIAN. Und generieren Sie eine #errorandere.
To1ne
6
<endian.h>ist nicht verfügbar unter Windows
Rustyx
2
Android- und Chromium- Projekte werden verwendet, endian.hsofern __APPLE__oder nicht _WIN32definiert.
patryk.beza
1
In OpenBSD 6.3 enthält <endian.h> #if BYTE_ORDER == LITTLE_ENDIAN(oder BIG_ENDIAN) keine Unterstriche vor den Namen. _BYTE_ORDERist nur für Systemheader. __BYTE_ORDERist nicht vorhanden.
George Koehler
@ To1ne Ich bezweifle, dass Endianness für Windows relevant ist, da Windows (zumindest derzeit) nur auf x86- und ARM-Computern ausgeführt wird. x86 ist immer LE und ARM kann für beide Architekturen konfiguriert werden.
SimonC
27

Um Endianness zur Laufzeit zu erkennen, müssen Sie in der Lage sein, auf den Speicher zu verweisen. Wenn Sie sich an Standard C halten, ist für die Deklaration einer Variablen im Speicher eine Anweisung erforderlich, für die Rückgabe eines Werts ist jedoch ein Ausdruck erforderlich. Ich weiß nicht, wie man das in einem einzelnen Makro macht - deshalb hat gcc Erweiterungen :-)

Wenn Sie bereit sind, eine .h-Datei zu haben, können Sie diese definieren

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

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

und dann können Sie das ENDIANNESSMakro wie Sie wollen verwenden.

Norman Ramsey
quelle
6
Ich mag das, weil es die Existenz einer anderen Endianness als klein und groß anerkennt.
Alok Singhal
6
Apropos, es könnte sich lohnen, das Makro INT_ENDIANNESS oder sogar UINT32_T_ENDIANNESS aufzurufen, da es nur die Speicherdarstellung eines Typs testet. Es gibt einen ARM-ABI, bei dem integrale Typen Little-Endian sind, Doubles jedoch Middle-Endian (jedes Wort ist Little-Endian, aber das Wort mit dem Vorzeichenbit steht vor dem anderen Wort). Ich kann Ihnen sagen, dass das Compilerteam einen Tag lang für Aufregung gesorgt hat.
Steve Jessop
19

Wenn Sie sich nur auf den Präprozessor verlassen möchten, müssen Sie die Liste der vordefinierten Symbole herausfinden. Präprozessorarithmetik hat kein Konzept der Adressierung.

GCC auf Mac definiert __LITTLE_ENDIAN__oder__BIG_ENDIAN__

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

Dann können Sie weitere Präprozessor-bedingte Anweisungen hinzufügen, die auf der Plattformerkennung basieren, wie z #ifdef _WIN32.

Gregory Pakosz
quelle
6
GCC 4.1.2 unter Linux scheint diese Makros nicht zu definieren, obwohl GCC 4.0.1 und 4.2.1 sie unter Macintosh definieren. Es ist also keine zuverlässige Methode für die plattformübergreifende Entwicklung, selbst wenn Sie bestimmen dürfen, welcher Compiler verwendet werden soll.
Rob Kennedy
1
Oh ja, das liegt daran, dass es nur von GCC auf dem Mac definiert wird.
Gregory Pakosz
Hinweis: Mein GCC (auf Mac) definiert #define __BIG_ENDIAN__ 1und #define _BIG_ENDIAN 1.
clang 5.0.1 für OpenBSD / amd64 hat #define __LITTLE_ENDIAN__ 1. Dieses Makro scheint ein Clang-Feature zu sein, kein GCC-Feature. Der gccBefehl in einigen Macs ist nicht gcc, sondern klirrend.
George Koehler
GCC 4.2.1 auf Mac war damals GCC
Gregory Pakosz
15

Ich glaube, darum wurde gebeten. Ich habe dies nur auf einer kleinen Endian-Maschine unter msvc getestet. Jemand bitte auf einer Big-Endian-Maschine bestätigen.

    #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

Als Randnotiz (compilerspezifisch) können Sie mit einem aggressiven Compiler die Optimierung "Dead Code Elimination" verwenden, um den gleichen Effekt wie bei einer Kompilierungszeit #ifwie folgt zu erzielen :

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

Das Obige beruht auf der Tatsache, dass der Compiler die konstanten Werte zur Kompilierungszeit erkennt, den Code vollständig entfernt if (false) { ... }und den Code wie if (true) { foo(); }im foo();schlimmsten Fall ersetzt: Der Compiler führt die Optimierung nicht durch, Sie erhalten immer noch den richtigen Code, aber etwas langsamer.

ggpp23
quelle
Ich mag diese Methode, aber korrigiere mich, wenn ich falsch liege: Dies funktioniert nur, wenn Sie auf dem Computer kompilieren, für den Sie bauen, richtig?
leetNightshade
3
gcc gibt auch einen Fehler aufgrund von Zeichenkonstanten mit mehreren Zeichen aus. Also nicht tragbar.
Edward Falk
2
Welcher Compiler lässt Sie schreiben 'ABCD'?
Ryan Haining
2
Viele Compiler erlauben Multibyte-Zeichenkonstanten in entspannten Kompatibilitätsmodi, führen jedoch den oberen Teil mit aus clang -Wpedantic -Werror -Wall -ansi foo.cund es wird ein Fehler auftreten. (Clang und dies insbesondere: -Wfour-char-constants -Werror)
@ Edward Falk Es ist kein Fehler , eine mehrstellige Konstante im Code zu haben. Es ist implementierungsdefiniertes Verhalten C11 6.4.4.4. 10. gcc und andere können / dürfen je nach Einstellungen nicht warnen / fehlerhaft sein, aber es handelt sich nicht um einen C-Fehler. Es ist sicherlich nicht beliebt, Zeichenkonstanten mit mehreren Zeichen zu verwenden.
chux - Wiedereinsetzung Monica
10

Wenn Sie nach einem Test zur Kompilierungszeit suchen und gcc verwenden, können Sie Folgendes tun:

#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__

Weitere Informationen finden Sie in der gcc-Dokumentation .

Jérôme Pouiller
quelle
3
Dies ist definitiv die beste Antwort für alle, die gcc
rtpax verwenden.
2
__BYTE_ORDER__ist verfügbar seit GCC 4.6
Benoit Blanchon
8

Sie können tatsächlich mit einem zusammengesetzten Literal (C99) auf den Speicher eines temporären Objekts zugreifen:

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

Welcher GCC wird zur Kompilierungszeit ausgewertet?

u0b34a0f6ae
quelle
Ich mag das. Gibt es eine tragbare Methode zur Kompilierung, um festzustellen, dass Sie unter C99 kompilieren?
Edward Falk
1
Oh, und was ist, wenn es nicht GCC ist?
Edward Falk
1
@ EdwardFalk Ja. #if __STDC_VERSION__ >= 199901L.
Jens
7

Die 'C-Netzwerkbibliothek' bietet Funktionen für die Endianität. Nämlich htons (), htonl (), ntohs () und ntohl () ... wobei n "Netzwerk" (dh Big-Endian) und h "Host" ist (dh die Endianität der Maschine, auf der das ausgeführt wird) Code).

Diese scheinbaren 'Funktionen' werden (allgemein) als Makros definiert [siehe <netinet / in.h>], sodass kein Laufzeitaufwand für ihre Verwendung entsteht.

Die folgenden Makros verwenden diese 'Funktionen', um die Endianität zu bewerten.

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

Zusätzlich:

Das einzige Mal, dass ich jemals die Endianität eines Systems kennen muss, ist das Ausschreiben einer Variablen [in eine Datei / andere], die von einem anderen System mit unbekannter Endianität eingelesen werden kann (aus Gründen der plattformübergreifenden Kompatibilität) ) ... In solchen Fällen bevorzugen Sie möglicherweise die direkte Verwendung der Endian-Funktionen:

#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);
BlueChip
quelle
Dies beantwortet nicht wirklich die Frage, die nach einem schnellen Weg suchte, um Endianness zu bestimmen.
Oren
@Oren: In Bezug auf Ihre berechtigte Kritik habe ich Details vorangestellt, die die ursprüngliche Frage direkter ansprechen.
BlueChip
6

Verwenden Sie eine Inline-Funktion anstelle eines Makros. Außerdem müssen Sie etwas im Speicher speichern, was ein nicht so schöner Nebeneffekt eines Makros ist.

Sie können es mit einer statischen oder globalen Variablen wie folgt in ein kurzes Makro konvertieren:

static int s_endianess = 0;
#define ENDIANESS() ((s_endianess = 1), (*(unsigned char*) &s_endianess) == 0)
user231967
quelle
Ich denke, das ist das Beste, da es das Einfachste ist. Es wird jedoch nicht gegen gemischte
Endianer
1
Warum wird zunächst nicht s_endianessauf 1 gesetzt?
SquareRootOfTwentyThree
5

Während es kein portables #define oder etwas gibt, auf das Sie sich verlassen können, bieten Plattformen Standardfunktionen für die Konvertierung zu und von Ihrem 'Host'-Endian.

Im Allgemeinen speichern Sie - auf Festplatte oder im Netzwerk - mithilfe von 'Netzwerk-Endian' ( BIG- Endian) und lokaler Berechnungen mit Host-Endian (auf x86 LITTLE- Endian). Sie verwenden htons()und ntohs()und Freunde, um zwischen den beiden zu konvertieren.

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

quelle
6
Dies erzeugt auch ausführbaren Code, keine Konstante. Sie konnten nicht "#if IS_BIG_ENDIAN"
Edward Falk
Ich mag diese Lösung, da sie meines Wissens nicht auf undefiniertem Verhalten nach C / C ++ - Standards beruht. Es ist keine Kompilierungszeit, aber die einzige Standardlösung dafür ist das Warten auf c ++ 20 std :: endian
ceztko
4

Vergessen Sie nicht, dass Endianness nicht die ganze Geschichte ist - die Größe von charmöglicherweise nicht 8 Bit (z. B. DSPs), die Negation von Zweierkomplementen ist nicht garantiert (z. B. Cray), möglicherweise ist eine strikte Ausrichtung erforderlich (z. B. SPARC, auch ARM springt in die Mitte) -endian wenn nicht ausgerichtet) usw. usw.

Es ist möglicherweise besser, stattdessen auf eine bestimmte CPU-Architektur abzuzielen.

Beispielsweise:

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

Beachten Sie, dass diese Lösung leider auch nicht ultra-portabel ist, da sie von compilerspezifischen Definitionen abhängt (es gibt keinen Standard, aber hier ist eine schöne Zusammenstellung solcher Definitionen).

Rustyx
quelle
3

Versuche dies:

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

   TEST;
}
Prasoon Saurav
quelle
2

Bitte beachten Sie, dass die meisten Antworten hier nicht portierbar sind, da Compiler diese Antworten heute in der Kompilierungszeit auswerten (abhängig von der Optimierung) und einen bestimmten Wert basierend auf einer bestimmten Endianness zurückgeben, während die tatsächliche Maschinenendianness abweichen kann. Die Werte, auf denen die Endianness getestet wird, erreichen niemals den Systemspeicher, sodass der tatsächlich ausgeführte Code unabhängig von der tatsächlichen Endianness das gleiche Ergebnis zurückgibt.

Zum Beispiel , in ARM Cortex-M3 wird das implementierte endianness in einer Status - Bit AIRCR.ENDIANNESS reflektieren und Compiler diesen Wert in der Kompilierung nicht wissen können.

Kompilierungsausgabe für einige der hier vorgeschlagenen Antworten:

https://godbolt.org/z/GJGNE2 für diese Antwort,

https://godbolt.org/z/Yv-pyJ für diese Antwort und so weiter.

Um es zu lösen, müssen Sie das volatileQualifikationsmerkmal verwenden. Yogeesh H TDie Antwort ist die nächstgelegene für den heutigen Gebrauch im wirklichen Leben. Da Christophjedoch eine umfassendere Lösung vorgeschlagen wird, würde eine geringfügige Korrektur seiner Antwort die Antwort vervollständigen. Fügen Sie einfach volatiledie Gewerkschaftserklärung hinzu : static const volatile union.

Dies würde das Speichern und Lesen aus dem Speicher sicherstellen, was zur Bestimmung der Endianness erforderlich ist.

user2162550
quelle
2

Wenn Sie den Präprozessor #defines sichern

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

Normalerweise finden Sie Dinge, die Ihnen helfen. Mit Kompilierzeitlogik.

#define __LITTLE_ENDIAN__ 1
#define __BYTE_ORDER__ __ORDER_LITTLE_ENDIAN__

Verschiedene Compiler können jedoch unterschiedliche Definitionen haben.

Sam P.
quelle
0

Meine Antwort ist nicht wie gefragt, aber es ist wirklich einfach zu finden, ob Ihr System Little Endian oder Big Endian ist?

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
quelle
0

C Code zur Überprüfung, ob ein System Little-Endian oder Big-Indian ist.

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
quelle
-3

Makro, um Endiannes zu finden

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

oder

#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
quelle
3
Das erste Makro ist falsch und gibt immer "Big-Endian" zurück. Die Bitverschiebung wird durch Endianness nicht beeinflusst - Endianness wirkt sich nur auf Lese- und Speichervorgänge im Speicher aus.
GaspardP