Определение порядка следования байтов во время компиляции

есть ли безопасный, портативный способ определить (во время компиляции) endianness платформы, на которой компилируется моя программа? Я пишу на C.

[редактирование] Спасибо за ответы, я решил придерживаться решения runtime!

10 ответов


это для проверки времени компиляции

вы можете использовать информацию из файла заголовка boost endian.hpp, который охватывает множество платформ.

edit для проверки выполнения

bool isLittleEndian()
{
    short int number = 0x1;
    char *numPtr = (char*)&number;
    return (numPtr[0] == 1);
}

создайте целое число и прочитайте его первый байт (наименее значимый байт). Если этот байт равен 1, то система мало endian, иначе это большой endian.

редактировать думал о

да вы можете столкнуться с потенциальной проблемой на некоторых платформах (не могу придумать), где sizeof(char) == sizeof(short int). Вы можете использовать многобайтовые интегральные типы фиксированной ширины, доступные в <stdint.h>, или если ваша платформа не имеет его, снова вы можете адаптировать заголовок boost для вашего использования:stdint.hpp


чтобы ответить на первоначальный вопрос времени компиляции проверьте, нет стандартизированного способа сделать это, который будет работать во всех существующих и всех будущих компиляторах, потому что ни один из существующих стандартов C, C++ и POSIX не определяет макросы для обнаружения endianness.

но если вы хотите ограничить себя некоторым известным набором компиляторов, вы можете просмотреть каждую из этих компиляторов, чтобы узнать, какие предопределенные макросы (если таковые имеются) они используют для определения порядок байтов. на этой странице перечисляет несколько макросов, которые вы можете найти, поэтому вот код, который будет работать для них:

#if defined(__BYTE_ORDER) && __BYTE_ORDER == __BIG_ENDIAN || \
    defined(__BIG_ENDIAN__) || \
    defined(__ARMEB__) || \
    defined(__THUMBEB__) || \
    defined(__AARCH64EB__) || \
    defined(_MIBSEB) || defined(__MIBSEB) || defined(__MIBSEB__)
// It's a big-endian target architecture
#elif defined(__BYTE_ORDER) && __BYTE_ORDER == __LITTLE_ENDIAN || \
    defined(__LITTLE_ENDIAN__) || \
    defined(__ARMEL__) || \
    defined(__THUMBEL__) || \
    defined(__AARCH64EL__) || \
    defined(_MIPSEL) || defined(__MIPSEL) || defined(__MIPSEL__)
// It's a little-endian target architecture
#else
#error "I don't know what architecture this is!"
#endif

если вы не можете найти, какие предопределенные макросы использует ваш компилятор из своей документации, вы также можете попытаться заставить его выплюнуть свой полный список предопределенных макросов и угадать оттуда, что будет работать (ищите что-нибудь с ENDIAN, порядком или именем архитектуры процессора в нем). на этой странице перечисляет несколько методов для делать что в разных компиляторах:

Compiler                   C macros                         C++ macros
Clang/LLVM                 clang -dM -E -x c /dev/null      clang++ -dM -E -x c++ /dev/null
GNU GCC/G++                gcc   -dM -E -x c /dev/null      g++     -dM -E -x c++ /dev/null
Hewlett-Packard C/aC++     cc    -dM -E -x c /dev/null      aCC     -dM -E -x c++ /dev/null
IBM XL C/C++               xlc   -qshowmacros -E /dev/null  xlc++   -qshowmacros -E /dev/null
Intel ICC/ICPC             icc   -dM -E -x c /dev/null      icpc    -dM -E -x c++ /dev/null
Microsoft Visual Studio (none)                              (none)
Oracle Solaris Studio      cc    -xdumpmacros -E /dev/null  CC      -xdumpmacros -E /dev/null
Portland Group PGCC/PGCPP  pgcc  -dM -E                     (none)

наконец, чтобы завершить его, компиляторы Microsoft Visual C/C++ являются нечетными и не имеют ничего из вышеперечисленного. К счастью, они задокументировали свои предопределенные макросы здесь, и вы можете использовать архитектуру целевого процессора для вывода endianness. В то время как все поддерживаемые в настоящее время процессоры в Windows мало-endian (_M_IX86, _M_X64, _M_IA64 и _M_ARM мало-endian), некоторые исторически поддерживаемые процессоры, такие как PowerPC (_M_PPC) были big-endian. Но более релевантно, Xbox 360-это большая конечная машина PowerPC, поэтому, если вы пишете заголовок кросс-платформенной библиотеки, не помешает проверить _M_PPC.


С помощью C99 вы можете выполнить проверку как:

#define I_AM_LITTLE (((union { unsigned x; unsigned char c; }){1}).c)

условные конструкции типа if (I_AM_LITTLE) будет оцениваться во время компиляции и позволит компилятору оптимизировать целые блоки.

у меня нет ссылки сразу на то, является ли это строго говоря константное выражение в C99 (что позволило бы использовать его в инициализаторах для данных статического хранения), но если нет, это следующая лучшая вещь.


интересное чтение из C FAQ:

вы, вероятно, не можете. Обычные методы обнаружения endianness задействуйте указатели или массивы char или, возможно, объединения, но препроцессор арифметика использует только длинные целые числа, и понятия адресация. Еще одна заманчивая возможность-что-то вроде

  #if 'ABCD' == 0x41424344

но это тоже ненадежно.


Я хотел бы расширить ответы для предоставления


Не во время компиляции, но, возможно, во время выполнения. Вот функция C, которую я написал, чтобы определить endianness:

/*  Returns 1 if LITTLE-ENDIAN or 0 if BIG-ENDIAN  */
#include <inttypes.h>
int endianness()
{
  union { uint8_t c[4]; uint32_t i; } data;
  data.i = 0x12345678;
  return (data.c[0] == 0x78);
}

С наконец, однострочное обнаружение endianness в препроцессоре C:

#include <stdint.h>

#define IS_BIG_ENDIAN (*(uint16_t *)"\xff" < 0x100)

любой приличный оптимизатор разрешит это во время компиляции. НКУ не менее!--1-->.

конечно stdint.h это C99. Для переносимости ANSI / C89 см.Мгновенный C9x библиотека.


однажды я использовал такую конструкцию:

uint16_t  HI_BYTE  = 0,
          LO_BYTE  = 1;
uint16_t  s = 1;

if(*(uint8_t *) &s == 1) {   
   HI_BYTE = 1;
   LO_BYTE = 0;
} 

pByte[HI_BYTE] = 0x10;
pByte[LO_BYTE] = 0x20;

gcc с -O2 смог сделать это полностью время компиляции. Это означает, что HI_BYTE и LO_BYTE переменные были полностью заменены, и даже доступ к pByte был заменен в ассемблере эквивалентом *(unit16_t *pByte) = 0x1020;.

это как время компиляции, как он получает.


насколько мне известно, нет, не во время компиляции.

во время выполнения вы можете выполнять тривиальные проверки, такие как установка многобайтового значения в известную битовую строку и проверять, какие байты это приводит. Например, используя объединение,

typedef union {
    uint32_t word;
    uint8_t bytes[4];
} byte_check;

или литья,

uint32_t word;
uint8_t * bytes = &word;

пожалуйста заметьте что для вполне портативных проверок endianness, вам нужно учесть и big-endian, little-endian и смешанн-endian системы.


EDIT2: этот метод не работает. Представление многобайтовой константы специфично для компилятора / платформы и не может быть надежно использовано. Ссылка interjay дала (http://www.ideone.com/LaKpj) дает пример, где он терпит неудачу. На Solaris/SPARC тот же компилятор gcc 4.3.3 дает правильный ответ, но компилятор SUNStudio 12 будет иметь то же поведение, что и gcc 4.3.4 на x86, используемый по этой ссылке.

Итак, мы можем заключить, что по-прежнему нет хорошего использования многобайтовых характер

Нашел этот новый метод, который имеет то преимущество, что он прост и время компиляции.

switch('AB') {
  case 0x4142: printf("ASCII  Big endian\n"); break;
  case 0x4241: printf("ASCII  Little endian\n"); break;
  case 0xC1C2: printf("EBCDIC Big endian\n"); break;
  case 0xC2C1: printf("EBCDIC Little endian\n"); break;
}

EDIT:

нашел даже способ сделать это в предпроцессоре:

#if 'AB' == 0x4142
#error "ASCII  Big endian\n"
#elif 'AB' == 0x4241
#error "ASCII  Little endian\n"
#elif 'AB' == 0xC1C2
#error "EBCDIC Big endian\n"
#elif 'AB' == 0xC2C1
#error "EBCDIC Little endian\n"
#else
#error "unknown coding and endianness\n"
#endif

и прежде чем кто-то спросит, многобайтовые символьные константы являются ANSI-C (даже C90), но определены реализацией. Вот первое полезное приложение, которое я нашел для них.