C Definicja makra określająca maszynę typu big endian czy little endian?

107

Czy istnieje jednowierszowa definicja makra określająca endianness maszyny? Używam następującego kodu, ale konwersja go na makro byłaby zbyt długa.

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

    return (test_endian[0] == 0);
}
manav mn
źródło
2
Dlaczego nie dołączyć tego samego kodu do makra?
ostry ząb
4
Nie można przenośnie określić endianness za pomocą samego preprocesora C. Chcesz też 0zamiast NULLw swoim ostatnim teście i zmienić jeden z test_endianobiektów na inny :-).
Alok Singhal,
2
Dlaczego potrzebne jest makro? Funkcja inline zrobiłaby to samo i jest znacznie bezpieczniejsza.
ostry ząb
13
@Sharptooth, makro jest atrakcyjne, ponieważ jego wartość może być znana w czasie kompilacji, co oznacza, że ​​możesz użyć endianness swojej platformy, na przykład do sterowania tworzeniem szablonów, a może nawet wybrać różne bloki kodu za pomocą #ifdyrektywy.
Rob Kennedy
3
To prawda, ale nieefektywne. Jeśli mam procesor little-endian i piszę dane little-endian na kablu lub do pliku, zdecydowanie wolałbym unikać rozpakowywania i przepakowywania danych bez celu. Zwykłem zarabiać na życie z pisania sterowników wideo. Jest to niezwykle ważne podczas pisania pikseli do karty graficznej w celu optymalizacji każde miejsce można.
Edward Falk,

Odpowiedzi:

102

Kod obsługujący dowolne rozkazy bajtów, gotowy do umieszczenia w pliku o nazwie 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

Sprawdziłbyś systemy little endian za pośrednictwem

O32_HOST_ORDER == O32_LITTLE_ENDIAN
Christoph
źródło
11
To jednak nie pozwala ci zdecydować o endian-ness przed uruchomieniem. Poniższe nie można skompilować, ponieważ. / ** isLittleEndian :: result -> 0 or 1 * / struct isLittleEndian {enum isLittleEndianResult {result = (O32_HOST_ORDER == O32_LITTLE_ENDIAN)}; };
user48956
3
Czy niemożliwe jest uzyskanie wyniku przed uruchomieniem?
k06a
8
Dlaczego char? Lepsze użycie uint8_ti niepowodzenie, jeśli ten typ nie jest dostępny (co można sprawdzić za pomocą#if UINT8_MAX ). Zauważ, że CHAR_BITjest niezależny od uint8_t.
Andreas Spindler
2
To jest UB w c ++: stackoverflow.com/questions/11373203/…
Lyberta
3
Pozwólcie, że dodam jeszcze jeden do miksu, dla kompletności: O32_HONEYWELL_ENDIAN = 0x02030001ul /* Honeywell 316 */
Edward Falk
49

Jeśli masz kompilator obsługujący literały złożone C99:

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

lub:

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

Generalnie jednak powinieneś spróbować napisać kod, który nie zależy od endianness platformy hosta.


Przykład implementacji niezależnej od endianności hosta 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];
}
kawiarnia
źródło
3
„powinieneś spróbować napisać kod, który nie jest zależny od endianness platformy hosta”. Niestety mój apel „Wiem, że piszemy warstwę kompatybilną z POSIX, ale nie chcę implementować ntoh, ponieważ zależy to od endianizmu platformy hosta”, zawsze padał głuchy ;-). Obsługa formatu grafiki i kod konwersji to inny główny kandydat, którego widziałem - nie chcesz opierać wszystkiego na ciągłym wywoływaniu ntohl.
Steve Jessop
5
Możesz zaimplementować ntohlw sposób, który nie zależy od endianness platformy hosta.
kawiarnia
1
@caf jak napisałbyś ntohl w sposób niezależny od hosta?
Hayri Uğur Koltuk
3
@AliVeli: Dodałem przykładową implementację do odpowiedzi.
kawiarnia
6
Powinienem również dodać, że "(* (uint16_t *)" \ 0 \ xff "<0x100)" nie zostanie skompilowane w stałą, bez względu na to, jak bardzo optymalizuję, przynajmniej z gcc 4.5.2. Zawsze tworzy kod wykonywalny.
Edward Falk,
43

Nie ma standardu, ale w wielu systemach, w tym <endian.h>, podaje kilka definicji, których należy szukać.

Ignacio Vazquez-Abrams
źródło
30
Przetestuj endianness za pomocą #if __BYTE_ORDER == __LITTLE_ENDIANi #elif __BYTE_ORDER == __BIG_ENDIAN. I generuj w #errorinny sposób.
To1ne
6
<endian.h>nie jest dostępny w systemie Windows
rustyx
2
Projekty na Androida i Chromium używają, endian.hchyba że zdefiniowano __APPLE__lub _WIN32.
patryk.beza
1
W OpenBSD 6.3 <endian.h> zapewnia #if BYTE_ORDER == LITTLE_ENDIAN(lub BIG_ENDIAN) bez podkreślenia przed nazwami. _BYTE_ORDERdotyczy tylko nagłówków systemowych. __BYTE_ORDERnie istnieje.
George Koehler
@ To1ne Wątpię, czy Endianness jest odpowiedni dla Windows, ponieważ Windows (przynajmniej obecnie) działa tylko na maszynach x86 i ARM. x86 zawsze jest LE, a ARM jest konfigurowalne do korzystania z dowolnej architektury.
SimonC,
27

Aby wykryć endianness w czasie wykonywania, musisz być w stanie odwołać się do pamięci. Jeśli trzymasz się standardowego C, deklarowanie zmiennej w pamięci wymaga instrukcji, ale zwrócenie wartości wymaga wyrażenia. Nie wiem, jak to zrobić w pojedynczym makrze - dlatego gcc ma rozszerzenia :-)

Jeśli chcesz mieć plik .h, możesz zdefiniować

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

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

a następnie możesz używać ENDIANNESSmakra, jak chcesz.

Norman Ramsey
źródło
6
Podoba mi się to, ponieważ uznaje istnienie endianizmu innego niż małe i duże.
Alok Singhal,
6
Skoro o tym mowa, warto wywołać makro INT_ENDIANNESS, a nawet UINT32_T_ENDIANNESS, ponieważ testuje ono tylko reprezentację pamięci jednego typu. Istnieje ARM ABI, w którym typy całkowite to little-endian, ale podwójne są środkowo-endianowe (każde słowo to little-endian, ale słowo z bitem znaku występuje przed drugim słowem). Mogę ci powiedzieć, że wywołało to podekscytowanie w zespole kompilatorów przez jeden dzień lub dwa.
Steve Jessop
19

Jeśli chcesz polegać tylko na preprocesorze, musisz znaleźć listę predefiniowanych symboli. Arytmetyka preprocesora nie ma pojęcia adresowania.

GCC na Macu definiuje __LITTLE_ENDIAN__lub__BIG_ENDIAN__

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

Następnie możesz dodać więcej dyrektyw warunkowych preprocesora na podstawie wykrywania platformy, np #ifdef _WIN32.

Gregory Pakosz
źródło
6
Wydaje się, że GCC 4.1.2 w systemie Linux nie definiuje tych makr, chociaż GCC 4.0.1 i 4.2.1 definiują je na komputerach Macintosh. Dlatego nie jest to niezawodna metoda tworzenia programów na wiele platform, nawet jeśli masz prawo decydować, którego kompilatora użyć.
Rob Kennedy
1
o tak, to dlatego, że jest zdefiniowane tylko przez GCC na Macu.
Gregory Pakosz
Uwaga: My GCC (na Macu) definiuje #define __BIG_ENDIAN__ 1i #define _BIG_ENDIAN 1.
clang 5.0.1 dla OpenBSD / amd64 ma #define __LITTLE_ENDIAN__ 1. To makro wydaje się być funkcją clang, a nie funkcją gcc. gccKomenda w niektórych komputerach Mac nie jest gcc, to dzyń.
George Koehler
GCC 4.2.1 na Macu było wtedy GCC
Gregory Pakosz
15

Uważam, że o to właśnie proszono. Przetestowałem to tylko na małej maszynie endian pod msvc. Ktoś proszę potwierdzić na maszynie 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

Na marginesie (specyficzne dla kompilatora), z agresywnym kompilatorem możesz użyć optymalizacji "eliminacji martwego kodu", aby osiągnąć taki sam efekt jak czas kompilacji, #iftaki jak ten:

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

Powyższe opiera się na fakcie, że kompilator rozpoznaje wartości stałe w czasie kompilacji, całkowicie usuwa kod w if (false) { ... }środku i zastępuje kod jak if (true) { foo(); }w foo();przypadku najgorszego przypadku: kompilator nie przeprowadza optymalizacji, nadal otrzymujesz poprawny kod, ale nieco wolniej.

ggpp23
źródło
Podoba mi się ta metoda, ale popraw mnie, jeśli się mylę: działa tylko wtedy, gdy kompilujesz na maszynie, dla której budujesz, prawda?
leetNightshade
3
gcc zgłasza również błąd związany z wieloznakowymi stałymi. Dlatego nie przenośny.
Edward Falk,
2
jaki kompilator pozwala ci pisać 'ABCD'?
Ryan Haining
2
Wiele kompilatorów zezwala na wielobajtowe stałe znakowe w łagodnych trybach zgodności, ale uruchamia górną część z clang -Wpedantic -Werror -Wall -ansi foo.ci spowoduje to błąd. (Szczęk, a to w szczególności: -Wfour-char-constants -Werror)
@Edward Falk Posiadanie w kodzie wieloznakowej stałej nie jest błędem . Jest to zachowanie zdefiniowane w implementacji. C11 6.4.4.4. 10. gcc i inne mogą / nie mogą ostrzegać / błędnie w zależności od ustawień, ale nie jest to błąd C. Z pewnością nie jest popularne stosowanie stałych wieloznakowych.
chux - Przywróć Monikę
10

Jeśli szukasz testu czasu kompilacji i używasz gcc, możesz wykonać:

#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__

Więcej informacji znajdziesz w dokumentacji gcc .

Jérôme Pouiller
źródło
3
To zdecydowanie najlepsza odpowiedź dla każdego, kto używa gcc
rtpax.
2
__BYTE_ORDER__jest dostępny od GCC 4.6
Benoit Blanchon
8

Państwo może w rzeczywistości dostęp do pamięci tymczasowej obiektu za pomocą dosłownym związek (C99):

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

Które GCC oceni w czasie kompilacji.

u0b34a0f6ae
źródło
Lubię to. Czy istnieje przenośny sposób na sprawdzenie w czasie kompilacji, że kompilujesz pod C99?
Edward Falk,
1
Aha, a co jeśli to nie jest GCC?
Edward Falk
1
@EdwardFalk Yes. #if __STDC_VERSION__ >= 199901L.
Jens,
7

„Biblioteka sieci C” oferuje funkcje do obsługi endii. Mianowicie htons (), htonl (), ntohs () i ntohl () ... gdzie n to "sieć" (tj. Big-endian), a h to "host" (tj. Endian'ness maszyny, na której działa kod).

Te pozorne „funkcje” są (powszechnie) definiowane jako makra [patrz <netinet / in.h>], więc ich używanie nie wiąże się z żadnymi kosztami wykonania.

Poniższe makra używają tych „funkcji” do oceny endian „ness ”.

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

Dodatkowo:

Jedynym przypadkiem, w którym kiedykolwiek muszę poznać endian'ness systemu, jest zapisanie zmiennej [do pliku / innego], która może być wczytywana przez inny system o nieznanym endian'ness (dla kompatybilności między platformami ) ... W takich przypadkach możesz preferować bezpośrednie użycie funkcji 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);
Blue chip
źródło
To tak naprawdę nie odpowiada na pytanie, które szukało szybkiego sposobu na określenie endianizmu.
Oren,
@Oren: Odnosząc się do twojej uzasadnionej krytyki, dodałem na początku szczegół, który bardziej bezpośrednio odnosi się do pierwotnego pytania.
BlueChip
6

Użyj funkcji wbudowanej zamiast makra. Poza tym trzeba coś zapisać w pamięci, co jest niezbyt przyjemnym efektem ubocznym makra.

Możesz przekonwertować je na krótkie makro za pomocą zmiennej statycznej lub globalnej, na przykład:

static int s_endianess = 0;
#define ENDIANESS() ((s_endianess = 1), (*(unsigned char*) &s_endianess) == 0)
user231967
źródło
Myślę, że to jest najlepsze, ponieważ jest najprostsze. jednak nie jest to test przeciwko mieszanemu
endianowi
1
Dlaczego s_endianessna początku nie jest ustawiona wartość 1?
SquareRootOfTwentyThree
5

Chociaż nie ma przenośnego #define lub czegoś, na czym można by polegać, platformy zapewniają standardowe funkcje do konwersji do iz endianu hosta.

Ogólnie rzecz biorąc, przechowujesz - na dysk lub w sieci - używając „endian sieci”, czyli BIG endian, oraz lokalnych obliczeń przy użyciu hosta endian (który na x86 to LITTLE endian). Używasz htons()i ntohs()i znajomych do konwersji między nimi.

Będzie
źródło
4
#include <stdint.h>
#define IS_LITTLE_ENDIAN (*(uint16_t*)"\0\1">>8)
#define IS_BIG_ENDIAN (*(uint16_t*)"\1\0">>8)

źródło
6
To również generuje kod wykonywalny, a nie stałą. Nie mogłeś zrobić „#if IS_BIG_ENDIAN”
Edward Falk,
Podoba mi się to rozwiązanie, ponieważ nie opiera się na niezdefiniowanym zachowaniu standardów C / C ++, o ile rozumiem. To nie czas kompilacji, ale jedyne standardowe rozwiązanie czeka na c ++ 20 std :: endian
ceztko
4

Nie zapominaj, że endianness to nie wszystko - rozmiar charmoże nie wynosić 8 bitów (np. DSP), negacja dopełniacza do dwóch nie jest gwarantowana (np. Cray), może być wymagane ścisłe wyrównanie (np. SPARC, również ARM sprężyny w środku -endian gdy niewyrównane) itp., itp.

Lepszym pomysłem może być wybranie określonej architektury procesora zamiast tego .

Na przykład:

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

Zwróć uwagę, że to rozwiązanie również nie jest niestety ultraprzenośne, ponieważ zależy od definicji specyficznych dla kompilatora (nie ma standardu, ale tutaj jest ładna kompilacja takich definicji).

rustyx
źródło
3

Spróbuj tego:

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

   TEST;
}
Prasoon Saurav
źródło
2

Proszę zwrócić uwagę, że większość odpowiedzi tutaj nie jest przenośna, ponieważ dzisiejsze kompilatory ocenią te odpowiedzi w czasie kompilacji (zależy od optymalizacji) i zwrócą określoną wartość na podstawie określonej endianness, podczas gdy faktyczna endianness maszyny może się różnić. Wartości, na których testowana jest endianność, nigdy nie dotrą do pamięci systemowej, dlatego rzeczywisty wykonywany kod zwróci ten sam wynik, niezależnie od rzeczywistej endianności.

Na przykład w ARM Cortex-M3 zaimplementowana endianness będzie odzwierciedlać bit stanu AIRCR.ENDIANNESS i kompilator nie może znać tej wartości w czasie kompilacji.

Dane wyjściowe kompilacji niektórych z sugerowanych tutaj odpowiedzi:

https://godbolt.org/z/GJGNE2 w tym celu odpowiedzi,

https://godbolt.org/z/Yv-pyJ w tym celu odpowiedzi i tak dalej.

Aby go rozwiązać, musisz użyć volatilekwalifikatora. Yogeesh H T„s odpowiedź jest najbliższy dzisiejszego użytkowania prawdziwym życiu, ale ponieważ Christophsugeruje bardziej kompleksowe rozwiązania, niewielkie poprawki do jego odpowiedź może sprawić, że odpowiedź pełna, wystarczy dodać volatiledo deklaracji związków: static const volatile union.

Zapewniłoby to przechowywanie i czytanie z pamięci, co jest potrzebne do określenia endianizmu.

user2162550
źródło
2

Jeśli zrzucisz preprocesor #defines

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

Zwykle możesz znaleźć rzeczy, które ci pomogą. Z logiką czasu kompilacji.

#define __LITTLE_ENDIAN__ 1
#define __BYTE_ORDER__ __ORDER_LITTLE_ENDIAN__

Jednak różne kompilatory mogą mieć różne definicje.

Sam P.
źródło
0

Moja odpowiedź nie jest tak zadawana, ale naprawdę łatwo jest sprawdzić, czy Twój system to little endian czy big endian?

Kod:

#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
źródło
0

C Kod do sprawdzania, czy system jest typu little-endian czy big-indian.

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
źródło
-3

Makro do znalezienia endian

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

lub

#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
źródło
3
Pierwsze makro jest nieprawidłowe i zawsze zwraca „Big-Endian”. Endianness nie wpływa na przesunięcie bitów - endianness wpływa tylko na odczyty i zapisy w pamięci.
GaspardP