Programowe wykrywanie endianizmu w programie C ++


211

Czy istnieje programowy sposób na wykrycie, czy korzystasz z architektury big-endian czy little-endian? Muszę być w stanie napisać kod, który będzie wykonywany w systemie Intel lub PPC i używać dokładnie tego samego kodu (tj. Bez kompilacji warunkowej).


4
Dla kompletności, oto link do pytania kogoś innego o próbie zmierzenia endianizmu (w czasie kompilacji): stackoverflow.com/questions/280162/…
Faisal Vali

14
Dlaczego nie określić endianizmu w czasie kompilacji? Nie można tego zmienić w czasie wykonywania.
ephemient

3
AFAIK, nie ma niezawodnego i uniwersalnego sposobu na zrobienie tego. gcc.gnu.org/ml/gcc-help/2007-07/msg00342.html
user48956,

Odpowiedzi:


174

Nie podoba mi się metoda oparta na pisaniu na czcionkach - często będzie ostrzegana przez kompilator. Właśnie po to są związki!

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

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

Zasada jest równoważna przypadkowi typu sugerowanemu przez innych, ale jest to jaśniejsze - i zgodnie z C99, gwarantuje się, że jest poprawna. gcc woli to w porównaniu do bezpośredniego rzutowania wskaźnikiem.

Jest to również znacznie lepsze niż naprawianie endianizmu w czasie kompilacji - w przypadku systemu operacyjnego obsługującego wiele architektur (na przykład gruby plik binarny w systemie Mac OS X), będzie to działać zarówno w przypadku ppc / i386, jak i bardzo łatwo zepsuć wszystko inaczej .


51
Nie polecam nazywania zmiennej „bint” :)
mkb

42
czy jesteś pewien, że jest to dobrze zdefiniowane? W C ++ tylko jeden członek związku może być aktywny jednocześnie - tzn. Nie można przypisywać przy użyciu jednego członka i czytać przy użyciu innego (chociaż istnieje wyjątek dla struktur zgodnych z układem)
Faisal Vali

27
@Matt: Spojrzałem na Google, a bint wydaje się mieć znaczenie w języku angielskim, o którym nie wiedziałem :)
David Cournapeau

17
Przetestowałem to i zarówno w gcc 4.0.1, jak i gcc 4.4.1 wynik tej funkcji można określić w czasie kompilacji i traktować jako stałą. Oznacza to, że kompilator spadnie, jeśli gałęzie zależne wyłącznie od wyniku tej funkcji i nigdy nie zostaną zastosowane na danej platformie. Prawdopodobnie nie jest to prawdą w przypadku wielu implementacji htonl.
Wszechobecny

6
Czy to rozwiązanie jest naprawdę przenośne? Co jeśli CHAR_BIT != 8?
zorgit

80

Możesz to zrobić, ustawiając int i maskując bity, ale prawdopodobnie najłatwiejszym sposobem jest po prostu użycie wbudowanych operacji konwersji bajtów sieciowych (ponieważ kolejność bajtów w sieci jest zawsze duża).

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

Nieznaczne skrzypki mogą być szybsze, ale ten sposób jest prosty, prosty i całkiem niemożliwy do zepsucia.


1
Operacje konwersji sieci mogą być również wykorzystane do konwersji wszystkiego na duży endian, rozwiązując w ten sposób inne problemy, z którymi Jay może się spotkać.
Brian

6
@sharptooth - slow jest terminem względnym, ale tak, jeśli naprawdę problem stanowi prędkość, użyj go raz na początku programu i ustaw zmienną globalną z endianowością.
Eric Petroelje,

5
htonl ma inny problem: na niektórych platformach (Windows?) nie znajduje się w odpowiedniej bibliotece wykonawczej C, ale w dodatkowych bibliotekach sieciowych (gniazdo itp.). Jest to dość przeszkodą dla jednej funkcji, jeśli w innym przypadku nie potrzebujesz biblioteki.
David Cournapeau

7
Zauważ, że w Linuksie (gcc) htonl podlega ciągłemu zwijaniu w czasie kompilacji, więc wyrażenie tej formy w ogóle nie ma narzutu działania (tj. Jest stale składane do 1 lub 0, a następnie eliminacja martwego kodu usuwa inny oddział if)
bdonlan,

2
Ponadto na x86 htonl może być (i jest na Linuksie / gcc) bardzo efektywnie implementowany przy użyciu wbudowanego asemblera, szczególnie jeśli celujesz w mikro-architekturę z obsługą tej BSWAPoperacji.
bdonlan,

61

Zobacz ten artykuł :

Oto kod do określenia, jaki jest typ twojej maszyny

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

25
Pamiętaj, że zależy to od int i char różnej długości, co prawie zawsze ma miejsce, ale nie jest gwarantowane.
David Thornley,

10
Pracowałem na systemach wbudowanych, w których short int i char miały ten sam rozmiar ... Nie pamiętam, czy regularna int była tego samego rozmiaru (2 bajty), czy nie.
rmeador

2
dlaczego ta odpowiedź jest właściwie jedyną odpowiedzią, która NIE każe mi myśleć „koleś, co robisz?”, co ma miejsce w przypadku większości odpowiedzi tutaj: o
hanshenrik

2
@Shillard int musi być co najmniej tak duży, ale norma nie wymaga, aby znak był ograniczony do mniejszego! Jeśli spojrzysz na rodzinę TI F280x, odkryjesz, że CHAR_BIT ma 16 i sizeof (int) == sizeof (char), podczas gdy wspomniane limity są absolutnie w porządku ...
Aconcagua

5
Dlaczego nie użyć uint8_t i uint16_t?
Rodrigo

58

Możesz użyć, std::endianjeśli masz dostęp do kompilatora C ++ 20, takiego jak GCC 8+ lub Clang 7+.

Uwaga: std::endianrozpoczęła się <type_traits>, ale została przeniesiona do <bit>w 2019 roku w Kolonii spotkania. GCC 8, Clang 7, 8 i 9 mają go, <type_traits>podczas gdy GCC 9+ i Clang 10+ mają go <bit>.

#include <bit>

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
}

5
Jak wszyscy mam dostęp do wersji C ++ 17 i 20 wersji roboczych / propozycji, ale czy obecnie istnieje jakiś kompilator C ++ 20?
Xeverous,

@Xeverous Wymaga tylko wyliczeń o zasięgu, więc podejrzewam, że większość dostawców doda to do swojej implementacji stdlib jako jedną ze swoich wcześniejszych zmian.
Pharap,

@Xeverous GCC 8 został wydany i obsługuje go.
Lyberta

Spośród ponad 30 odpowiedzi na to pytanie wydaje się to jedyne, które jest całkowicie dokładne (z inną odpowiedzią, przynajmniej częściowo poprawną).
Widoczny

40

Zwykle odbywa się to w czasie kompilacji (szczególnie ze względu na wydajność) przy użyciu plików nagłówkowych dostępnych z kompilatora lub utwórz własny. W systemie Linux masz plik nagłówkowy „/usr/include/endian.h”


8
Nie mogę uwierzyć, że nie zostało to przegłosowane wyżej. To nie tak, że endianness zmieni się w skompilowanym programie, więc nigdy nie ma potrzeby przeprowadzania testu czasu wykonywania.
Dolda2000

@ Dolda2000 Potencjalnie może, zobacz tryby endian ARM.
Tyzoid

10
@Tyzoid: Nie, skompilowany program zawsze będzie działał w trybie endian, dla którego został skompilowany, nawet jeśli procesor jest w stanie to zrobić.
Dolda2000,

16

Zaskoczyłem, że nikt nie wspomniał o makrach, które domyślnie definiuje procesor wstępny. Chociaż będą się różnić w zależności od platformy; są o wiele czystsze niż pisanie własnych testów endian.

Na przykład; jeśli spojrzymy na wbudowane makra zdefiniowane przez GCC (na maszynie X86-64):

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

Na maszynie PPC dostaję:

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

( :| gcc -dM -E -x c -Magia drukuje wszystkie wbudowane makra).


7
Te makra w ogóle się nie wyświetlają. Na przykład w gcc 4.4.5 z repozytorium Redhat 6 uruchamianie echo "\n" | gcc -x c -E -dM - |& grep -i 'endian'nic nie zwraca, podczas gdy gcc 3.4.3 (w /usr/sfw/binkażdym razie) w Solarisie ma taką definicję. Widziałem podobne problemy na VxWorks Tornado (gcc 2.95) -vs- VxWorks Workbench (gcc 3.4.4).
Brian Vandenberg

15

Ehm ... Zaskakuje mnie, że nikt nie zdawał sobie sprawy, że kompilator po prostu zoptymalizuje test i poda ustalony wynik jako wartość zwracaną. To sprawia, że ​​wszystkie powyższe przykłady kodu są praktycznie bezużyteczne. Jedyne, co zostanie zwrócone, to endianness w czasie kompilacji! I tak, przetestowałem wszystkie powyższe przykłady. Oto przykład z MSVC 9.0 (Visual Studio 2008).

Czysty kod 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;
}

Demontaż

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

Być może możliwe jest wyłączenie DOWOLNEJ optymalizacji czasu kompilacji tylko dla tej funkcji, ale nie wiem. W przeciwnym razie może być możliwe zakodowanie go w zestawie, chociaż nie jest to przenośne. I nawet wtedy można to zoptymalizować. To sprawia, że ​​myślę, że potrzebuję naprawdę kiepskiego asemblera, zaimplementuj ten sam kod dla wszystkich istniejących procesorów / zestawów instrukcji i cóż ... nieważne.

Również ktoś tutaj powiedział, że endianizm nie zmienia się w czasie wykonywania. ŹLE. Istnieją maszyny typu bi-endian. Ich endianizm może się różnić podczas wykonywania. RÓWNIEŻ istnieje nie tylko Little Endian i Big Endian, ale także inne endianizmy (co za słowo).

Nienawidzę i uwielbiam jednocześnie kodować ...


11
Czy i tak nie musisz się ponownie kompilować, aby uruchomić na innej platformie?
bobobobo

2
Mimo, że działa dobrze dla MSVC, nie działa we wszystkich wersjach GCC we wszystkich okolicznościach. Dlatego „kontrola czasu wykonywania” w pętli krytycznej może być poprawnie nierozgałęziona w czasie kompilacji lub nie. Nie ma 100% gwarancji.
Cyan

21
Nie ma czegoś takiego jak procesor x86 typu big-endian. Nawet jeśli uruchomisz Ubuntu na biendianowym procesorze (takim jak ARM lub MIPS), pliki wykonywalne ELF są zawsze duże (MSB) lub małe (LSB) endian. Nie można tworzyć plików wykonywalnych biendian, więc nie są potrzebne żadne kontrole środowiska wykonawczego.
Fabel,

4
Aby wyłączyć optymalizację w tej metodzie, użyj „volatile union ...” Mówi kompilatorowi, że „u” można zmienić gdzie indziej i należy załadować dane
mishmashru,

1
Aby ta funkcja zwróciła w czasie wykonywania inną wartość niż optymalizator, oblicza, że ​​oznacza to, że optymalizator jest uszkodzony. Czy mówisz, że istnieją przykłady skompilowanego zoptymalizowanego kodu binarnego, który może być przenośny na dwóch różnych architekturach o różnej endianowości, pomimo oczywistych założeń poczynionych przez optymalizator (w całym programie) podczas kompilacji, które wydają się być niezgodne z co najmniej jednym z nich architektury?
Scott

13

Zadeklaruj zmienną int:

int variable = 0xFF;

Teraz użyj wskaźników char * do różnych jego części i sprawdź, co jest w tych częściach.

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

W zależności od tego, który punkt wskazuje na bajt 0xFF, możesz teraz wykryć endianizm. Wymaga to sizeof (int)> sizeof (char), ale jest to z pewnością prawda dla omawianych platform.


8

Aby uzyskać więcej informacji, możesz przeczytać ten artykuł dotyczący projektu kodowego Podstawowe pojęcia na temat Endianness :

Jak dynamicznie testować typ Endian w czasie wykonywania?

Jak wyjaśniono w często zadawanych pytaniach dotyczących animacji komputerowych, możesz użyć następującej funkcji, aby sprawdzić, czy kod działa w systemie Little- lub Big-Endian: Zwiń

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

Ten kod przypisuje wartość 0001h do 16-bitowej liczby całkowitej. Wskaźnik char jest następnie przypisywany do wskazania pierwszego (najmniej znaczącego) bajtu wartości całkowitej. Jeśli pierwszym bajtem liczby całkowitej jest 0x01h, to system to Little-Endian (0x01h znajduje się w najniższym lub najmniej znaczącym adresie). Jeśli jest to 0x00h, to system to Big-Endian.


6

Sposobem C ++ było użycie boosta , w którym kontrole i rzuty preprocesora są dzielone w bardzo dokładnie przetestowanych bibliotekach.

Biblioteka Predef (boost / predef.h) rozpoznaje cztery różne rodzaje endianizmu .

Endian Biblioteka miała zostać złożone w standardzie C ++ i obsługuje szeroki zakres operacji na danych endian wrażliwej.

Jak stwierdzono w odpowiedziach powyżej, Endianness będzie częścią c ++ 20.


1
Do Twojej wiadomości, link „cztery różne rodzaje endianizmu” jest zerwany,
Remy Lebeau

naprawiono i utworzono wiki
fuzzyTew

5

O ile nie używasz frameworka, który został przeniesiony do procesorów PPC i Intel, będziesz musiał wykonać kompilacje warunkowe, ponieważ platformy PPC i Intel mają zupełnie inną architekturę sprzętową, potoki, magistrale itp. To sprawia, że ​​kod asemblera jest zupełnie inny dwójka.

Jeśli chodzi o znalezienie endianizmu, wykonaj następujące czynności:

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

Otrzymasz albo tempChar na 0x12, albo 0x34, z którego poznasz endianness.


3
Polega to na tym, że krótki jest dokładnie 2 bajty, co nie jest gwarantowane.
sharptooth

3
Byłby to jednak całkiem bezpieczny zakład, oparty na dwóch architekturach podanych w pytaniu.
Daemin

8
Uwzględnij stdint.hi wykorzystaj int16_tw przyszłości dowód, że short jest inny na innej platformie.
Denise Skidmore,

4

Zrobiłbym coś takiego:

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

Wzdłuż tych linii otrzymasz efektywną czasowo funkcję, która wykonuje obliczenia tylko raz.


umiesz to wstawić? nie jestem pewien, czy wbudowane powodują wiele bloków pamięci zmiennych statycznych
aah134,

4

Jak wspomniano powyżej, używaj sztuczek związkowych.

Jest jednak kilka problemów z tymi zalecanymi powyżej, w szczególności, że niewyrównany dostęp do pamięci jest notorycznie powolny w przypadku większości architektur, a niektóre kompilatory nawet nie rozpoznają takich stałych predykatów, chyba że dopasują słowa.

Ponieważ zwykły test endian jest nudny, oto funkcja (szablon), która zmienia wejście / wyjście dowolnej liczby całkowitej zgodnie z twoją specyfikacją, niezależnie od architektury hosta.

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

Stosowanie:

Aby przekonwertować dane dane z Endian na Host, użyj:

host = endian(source, endian_of_source)

Aby przekonwertować z hosta endian na dany endian, użyj:

output = endian(hostsource, endian_you_want_to_output)

Wynikowy kod jest tak szybki, jak pisanie zestawu ręcznego na clang, na gcc jest nieco wolniejszy (rozwinięty i, <<, >>, | dla każdego bajtu), ale nadal przyzwoity.


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

1
Czy byłoby to równoważne? #define IS_BIGENDIAN() (*((char*) &((int){ 0x00ff })) == (0x00))
Emanuel

4

Nie używaj union!

C ++ nie zezwala na pisanie typu za pomocą unions!
Czytanie z pola unii, które nie było ostatnim polem, do którego napisano, jest niezdefiniowanym zachowaniem !
Wiele kompilatorów obsługuje to jako rozszerzenia, ale język nie daje żadnej gwarancji.

Zobacz tę odpowiedź, aby uzyskać więcej informacji:

https://stackoverflow.com/a/11996970


Istnieją tylko dwie poprawne odpowiedzi, które z pewnością są przenośne.

Pierwszą odpowiedzią, jeśli masz dostęp do systemu obsługującego C ++ 20,
jest użycie std::endianz <type_traits>nagłówka.

(W momencie pisania C ++ 20 nie został jeszcze wydany, ale chyba że coś wpłynie na std::endianwłączenie, będzie to preferowany sposób testowania endianizmu w czasie kompilacji od C ++ 20 wzwyż.)

C ++ 20 i więcej

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

Przed wersją C ++ 20 jedyną prawidłową odpowiedzią jest zapisanie liczby całkowitej, a następnie sprawdzenie pierwszego bajtu za pomocą znakowania punktowego.
W przeciwieństwie do użycia unions, jest to wyraźnie dozwolone przez system typów C ++.

Ważne jest również, aby pamiętać, że dla optymalnej przenośności static_castnależy użyć,
ponieważ reinterpret_castjest zdefiniowana implementacja.

Jeśli program próbuje uzyskać dostęp do zapisanej wartości obiektu za pośrednictwem wartości innej niż jeden z następujących typów, zachowanie jest niezdefiniowane: ... a charlub unsigned chartype.

C ++ 11 i dalsze

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 i więcej (bez wyliczenia)

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

3
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");

To jest inne rozwiązanie. Podobne do rozwiązania Andrew Hare.


3

niesprawdzone, ale moim zdaniem powinno to działać? bo to będzie 0x01 na małym endianie i 0x00 na dużym endianie?

bool runtimeIsLittleEndian(void)
{
 volatile uint16_t i=1;
 return  ((uint8_t*)&i)[0]==0x01;//0x01=little, 0x00=big
}

3

Ogłosić:

Mój początkowy post został niepoprawnie zadeklarowany jako „czas kompilacji”. Nie jest, w obecnym standardzie C ++ jest to wręcz niemożliwe. Constexpr NIE oznacza, że ​​funkcja zawsze wykonuje obliczenia w czasie kompilacji. Dzięki Richard Hodges za korektę.

czas kompilacji, bez makr, rozwiązanie 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;
}

2
Czy jest jakiś konkretny powód, dla którego użyłeś niepodpisanego znaku zamiast uint8_t?
Kevin

0 czasu narzutu ... podoba mi się!
hanshenrik

Wydaje mi się, że wykrywa to endiannes maszyny kompilacji, a nie cel?
hutorny

2
Czy to nie jest UB w C ++?
rr-

6
nie jest to legalne w kontekście constexpr. Nie możesz uzyskać dostępu do członka związku, który nie został zainicjowany bezpośrednio. Nie ma sposobu na legalne wykrycie endianizmu w czasie kompilacji bez magii preprocesora.
Richard Hodges

2

Możesz to również zrobić za pomocą preprocesora, używając czegoś takiego jak plik nagłówkowy boost, który można znaleźć w boost endian


1

O ile nagłówek endian nie jest tylko GCC, dostarcza makr, których możesz użyć.

#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!");
...

Nie są one __BYTE_ORDER__, __ORDER_LITTLE_ENDIAN__i __ORDER_BIG_ENDIAN__?
Xeverous,

1

Jeśli nie chcesz kompilacji warunkowej, możesz po prostu napisać niezależny kod endian. Oto przykład (wzięty z Roba Pike'a ):

Odczytywanie liczby całkowitej przechowywanej w little-endian na dysku w sposób niezależny od endian:

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

Ten sam kod, starając się uwzględnić endianizm maszyny:

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

Co za fajny pomysł! A teraz przenieśmy liczby całkowite przez gniazdo sieciowe na nieznane urządzenie.
Maksym Ganenko

@MaksymGanenko Nie otrzymuję twojego komentarza. Czy to ironia? Ja nie sugeruje, aby nie określić endianness z serializacji danych. Sugeruję, aby nie pisać kodu zależnego od endianizmu maszyny odbierającej dane.
fjardon

@MaksymGanenko Jeśli zagłosujesz, możesz wyjaśnić, dlaczego odpowiedź jest zła. Co najmniej, aby pomóc potencjalnym czytelnikom zrozumieć, dlaczego nie powinni stosować się do mojej odpowiedzi.
fjardon,


0

Co powiesz na to?

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

0

Oto kolejna wersja C. Definiuje makro wywoływane wicked_cast()do wstawiania tekstu za pomocą literałów C99 i niestandardowego __typeof__operatora.

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

Jeśli liczby całkowite są wartościami jednobajtowymi, endianness nie ma sensu i zostanie wygenerowany błąd czasu kompilacji.


0

Sposób, w jaki kompilatory C (przynajmniej wszyscy, których znam) działa na endianizm, musi być ustalony w czasie kompilacji. Nawet w przypadku procesorów biendian (takich jak ARM i MIPS) musisz wybrać endianness w czasie kompilacji. Co więcej, endianność jest zdefiniowana we wszystkich popularnych formatach plików dla plików wykonywalnych (takich jak ELF). Chociaż możliwe jest stworzenie binarnego obiektu blob z kodu biandian (może dla niektórych exploitów serwera ARM?), Prawdopodobnie należy to zrobić podczas montażu.


-1

Jak zauważył Coriiander, większość (jeśli nie wszystkie) tych kodów zostanie zoptymalizowana w czasie kompilacji, więc wygenerowane pliki binarne nie sprawdzą „endianizmu” w czasie wykonywania.

Zaobserwowano, że dany plik wykonywalny nie powinien działać z dwoma różnymi kolejnymi bajtami, ale nie mam pojęcia, czy tak jest zawsze, i wydaje mi się, że to sprawdzanie w czasie kompilacji. Więc zakodowałem tę funkcję:

#include <stdint.h>

int* _BE = 0;

int is_big_endian() {
    if (_BE == 0) {
        uint16_t* teste = (uint16_t*)malloc(4);
        *teste = (*teste & 0x01FE) | 0x0100;
        uint8_t teste2 = ((uint8_t*) teste)[0];
        free(teste);
        _BE = (int*)malloc(sizeof(int));
        *_BE = (0x01 == teste2);
    }
    return *_BE;
}

MinGW nie był w stanie zoptymalizować tego kodu, mimo że optymalizuje inne kody tutaj. Wydaje mi się, że dzieje się tak, ponieważ zostawiam „losową” wartość, która została przydzielona do mniejszej pamięci bajtowej (co najmniej 7 jej bitów), więc kompilator nie może wiedzieć, co to jest ta losowa wartość i nie optymalizuje funkcja jest wyłączona.

Zakodowałem również tę funkcję, aby kontrola była wykonywana tylko raz, a zwracana wartość jest przechowywana do następnych testów.


Po co przydzielać 4 bajty do pracy na wartości 2-bajtowej? Po co maskować nieokreśloną wartość 0x7FE? Po malloc()co w ogóle korzystać? to marnotrawstwo. I _BEjest (choć niewielki) wyciek pamięci i warunki wyścigu, które czekają na nadejście, korzyści dynamicznego buforowania wyniku nie są warte kłopotu. Zamiast tego zrobiłbym coś takiego: static const uint16_t teste = 1; int is_little_endian() { return (0x01 == ((uint8_t*)&teste)[0]); } int is_big_endian() { return (0x01 == ((uint8_t*)&teste)[1]); }prosty i skuteczny, a znacznie mniej pracy do wykonania w czasie wykonywania.
Remy Lebeau

@RemyLebeau, celem mojej odpowiedzi było stworzenie kodu, który nie jest zoptymalizowany przez kompilator. Jasne, twój kod jest znacznie prostszy, ale przy włączonych optymalizacjach po kompilacji stanie się po prostu stałą wartością logiczną. Jak powiedziałem w mojej odpowiedzi, tak naprawdę nie wiem, czy istnieje jakiś sposób na kompilację kodu C w taki sposób, że ten sam plik wykonywalny działa na obu zamówieniach bajtów, i byłem również ciekawy, czy mógłbym sprawdzić w czasie wykonywania pomimo trwających optymalizacji.
Tex Killer

@TexKiller to dlaczego po prostu nie wyłączyć optymalizacji kodu? Za pomocą volatilelub #pragmaitp.
Remy Lebeau

@RemyLebeau, nie znałem wtedy tych słów kluczowych i po prostu podjąłem to za małe wyzwanie, aby zapobiec optymalizacji kompilatora z tym, co wiedziałem.
Tex Killer

-1

chociaż nie ma szybkiego i standardowego sposobu na określenie tego, wygeneruje to:

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

-1

Zobacz Endianness - ilustracja kodu poziomu C.

// assuming target architecture is 32-bit = 4-Bytes
enum ENDIANNESS{ LITTLEENDIAN , BIGENDIAN , UNHANDLE };


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

-2

Przeglądałem podręcznik: System komputerowy: perspektywa programisty i istnieje problem z określeniem, który endian jest to program C.

Użyłem funkcji wskaźnika, aby to zrobić w następujący sposób:

#include <stdio.h>

int main(void){
    int i=1;
    unsigned char* ii = &i;

    printf("This computer is %s endian.\n", ((ii[0]==1) ? "little" : "big"));
    return 0;
}

Ponieważ int zajmuje 4 bajty, a char zajmuje tylko 1 bajt. Możemy użyć wskaźnika char, aby wskazać int z wartością 1. Zatem jeśli komputer jest małym endianem, char, na który wskazuje wskaźnik char, ma wartość 1, w przeciwnym razie jego wartość powinna wynosić 0.


poprawiłoby to użycie int32t.
transfer87

1
^ jeśli chcesz nitpick, najlepiej tutaj jest int16_fast_t. a aktualny kod @ Archimedes520 nie będzie działał na łuku, gdzie int jest natywnie int8;) (może to jednak w pierwszej kolejności być sprzeczne ze standardami c)
hanshenrik
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.