Определение сущности во время компиляции
Существует ли безопасный переносимый способ определения (во время компиляции) конечности платформы, с которой моя программа компилируется? Я пишу на 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);
}
Ответ 7
От Наконец, однострочное определение endianness в препроцессоре C:
#include <stdint.h>
#define IS_BIG_ENDIAN (*(uint16_t *)"\0\xff" < 0x100)
Любой достойный оптимизатор разрешит это во время компиляции. gcc делает в -O1
.
Конечно, stdint.h
- C99. Для переносимости ANSI/C89 см. Doug Gwyn Мгновенная библиотека C9x.
Ответ 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;
}
С другой стороны, как отмечалось в оригинальном постере, накладные расходы на проверку времени выполнения настолько малы (две строки кода и микросекунды времени), что вряд ли стоит пытаться делать это в препроцессоре. ,