определение и производительность endianess в C

У меня есть критически важный код C, который должен работать на разных платформах. Некоторые из них немного ориентированы, другие – великие.

Обнаружение endianess в настоящее время является несовершенным процессом, основанным на обнаружении макросов. Но трудно быть уверенным, что обнаружение макросов будет работать для всех комбинаций систем и компиляторов. Добро пожаловать в мир портативного кода.

Одним из относительно безопасных способов обнаружения endianess является использование теста времени выполнения и надеемся, что он будет оптимизирован компилятором. Что-то в этом роде:

static const int one = 1; #define IS_LITTLE_ENDIAN (*(char*)(&one)) 

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

Мой вопрос: всегда ли это так? Можем ли мы ожидать, что компилятор всегда правильно поймет этот тест и всегда оптимизирует его правильно? (предполагая -O2 / -O3 или эквивалентный уровень оптимизации, не применимый, конечно, для отладки кода)

Меня особенно беспокоит двухъядерный процессор , например, ARM. Так как такой CPU может быть либо большим, либо большим, либо ограниченным, в зависимости от параметров ОС, компилятору может быть сложно «провести проводку» такого endian-теста. С другой стороны, я не ожидаю, что приложение будет работать в «любом конечном режиме выбора»: я думаю, он должен быть скомпилирован для одной точной и окончательной endianess. Следовательно, IS_LITTLE_ENDIAN всегда должен совпадать.

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

[ Edit ] @Brandin предлагает «сохранить результат» макроса, сделав его переменной. Думаю, он предлагает что-то вроде этого:

 static const int one = 1; static const int isLittleEndian = *(char*)(&one); 

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

К сожалению, это не сработает. Вышеприведенная декларация приводит к следующей ошибке компиляции:

 error: initializer element is not constant 

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

@ Вариант HuStmpHrrr, используя соединение вместо этого, выглядит лучше: нет указателя указателя для оценки. К сожалению, он не работает лучше и приводит к той же ошибке компиляции.

Я предполагаю, что это связано с тем, что компиляторы не считаются достаточно простыми для использования в качестве значения для статической инициализации const.

Итак, мы вернулись к началу с макросом.

Та же идея, но другой трюк. этот код тоже будет работать.

 union { int num; char[sizeof(int)] bytes; } endian; endian.num = 1; 

затем используйте endian.bytes[0] чтобы судить.

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

endian.bytes[0] следует endian.bytes[0] до константы.

В любом случае, этот способ зависит от компилятора.

Не обращая внимания на второстепенную проблему, связанную с комментарием, связанным с комментарием @OliCharlesworth, я согласен с его духом: уверены ли вы, что законность действительно имеет значение, и если да, то почему?

  • Если это не имеет особого значения, портативное и, вероятно, очень быстрое решение – использовать memcpy , который будет копировать байты в порядке байтов хоста. Это означает, что файлы не будут взаимозаменяемы между большими и маленькими конечными машинами.

  • Вы можете преобразовать целое число в конкретную спецификацию, если это необходимо, как предложено в вышеупомянутой статье, выполняя операции с битовым сдвигом по значениям, независимо от их значения. Например, запись (value >> 8) & 0xff а затем (value >> 0) & 0xff в файл вместо самого необработанного значения будет записывать 16-битное значение в порядке большого числа символов всегда, и вы всегда будете читать верхние 8 бит (старший байт) 16-битного значения. Это, по сути, то, что функционирует как htonl do, за исключением того, что значение, возвращаемое функцией, может быть различным из-за возможности реструктуризации его базового представления байтов, которое отличается от простого записи сдвинутых байтов один за другим.

Без другого контекста, кроме «Я имею критически важный для работы код», любые другие рекомендации недоступны. Основная проблема, о которой я уже упоминал, является причиной того, что консистенция машины настолько важна для этой проблемы.

Вы можете использовать htonl для его обнаружения:

 #include  int main() { if (htonl(1) == 1) printf("big endian\n"); else printf("little endian\n"); }