Определение сущности во время компиляции

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

[EDIT] Спасибо за ответы, я решил придерживаться решения времени исполнения.

Ответы

Ответ 1

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

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

редактирование для проверки времени выполнения

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

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

редактировать Думая об этом

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

Ответ 2

Чтобы ответить на исходный вопрос проверки времени компиляции, нет стандартизованного способа сделать это, который будет работать во всех существующих и всех будущих компиляторах, потому что ни один из существующих стандартов 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 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 a little-endian target architecture
#else
#error "I don't know what architecture this is!"
#endif

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

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/С++ являются нечетными и не имеют ничего из вышеперечисленного. К счастью, они задокументировали свои предопределенные макросы здесь, и вы можете использовать целевую архитектуру процессора, чтобы сделать вывод о контенте. В то время как все поддерживаемые в настоящее время процессоры в Windows малориентированы (_M_IX86, _M_X64, _M_IA64 и _M_ARM являются малоприводными), некоторые исторически поддерживаемые процессоры, такие как PowerPC (_M_PPC), были большими -endian. Но, что более важно, Xbox 360 является машиной PowerPC большого размера, поэтому, если вы пишете кросс-платформенный заголовок библиотеки, не может повредить проверку на _M_PPC.

Ответ 3

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

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

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

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

Ответ 4

Интересное чтение из ЧаВо FAQ > :

Вероятно, вы не можете. Обычные методы определения сущности включать указатели или массивы char, или, возможно, объединения, но препроцессор арифметика использует только длинные целые числа, и нет понятия адресации. Другая заманчивая возможность - это что-то вроде

  #if 'ABCD' == 0x41424344

но это также не является надежным.

Ответ 5

Я хотел бы расширить ответы за предоставление функции constexpr для C++

union Mix {
    int sdat;
    char cdat[4];
};
static constexpr Mix mix { 0x1 };
constexpr bool isLittleEndian() {
    return mix.cdat[0] == 1;
}

Поскольку mix также является constexpr, это время компиляции и может использоваться в constexpr bool isLittleEndian(). Должно быть безопасным в использовании.

Обновление

Как отметил @Cheersandhth ниже, это кажется проблематичным.

Причина в том, что он не соответствует C++ 11-стандарту, где тип наказания запрещен. Всегда может быть активным только один член профсоюза. С помощью стандартного компилятора вы получите ошибку.

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

Обновление 2

Это предполагает, что int имеет размер 4 char с, что не всегда дается, поскольку @PetrVepřek правильно указано ниже. Чтобы сделать ваш код действительно переносимым, вы должны быть более умным здесь. Это должно быть достаточно для многих случаев, хотя. Обратите внимание, что sizeof(char) всегда является 1 по определению. Код выше предполагает sizeof(int)==4.

Ответ 6

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

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

Ответ 8

Я однажды использовал конструкцию, подобную этой:

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 acces был заменен в ассемблере эквивалентом *(unit16_t *pByte) = 0x1020;.

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

Ответ 9

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

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

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

или литье,

uint32_t word;
uint8_t * bytes = &word;

Обратите внимание, что для полностью переносных проверок проверки подлинности вам необходимо учитывать как системы с большими, так и мало-конечными и смешанными концами.

Ответ 10

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), но определены как реализация. Здесь первое полезное приложение, которое я нашел для них.

Ответ 11

Используйте CMake TestBigEndian как

INCLUDE(TestBigEndian)
TEST_BIG_ENDIAN(ENDIAN)
IF (ENDIAN)
    # big endian
ELSE (ENDIAN)
    # little endian
ENDIF (ENDIAN)

Ответ 12

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

#define II      0x4949     // arbitrary values != 1; examples are
#define MM      0x4D4D     // taken from the TIFF standard

int
#if defined __BYTE_ORDER__ && __BYTE_ORDER__ == __LITTLE_ENDIAN
     const host_endian = II;
# elif defined __BYTE_ORDER__ && __BYTE_ORDER__ == __BIG__ENDIAN
     const host_endian = MM;
#else
#define _no_BYTE_ORDER
     host_endian = 1;            // plain "int", not "int const" !
#endif

а затем в фактическом коде:

int main(int argc, char **argv) {
#ifdef _no_BYTE_ORDER
    host_endian = * (char *) &host_endian ? II : MM;
#undef _no_BYTE_ORDER
#endif

// .... your code here, for instance:
printf("Endedness: %s\n", host_endian == II ? "little-endian"
                                            : "big-endian");

return 0;
}

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