Подтвердить что ты не робот

Как написать endin agnostic C/С++-код?

Я сделал несколько поисковых запросов и не нашел хорошей статьи по этому вопросу. Что я должен отслеживать при реализации приложения, которое я хочу быть энтиматическим?

4b9b3361

Ответ 1

Это может быть хорошей статьей для вас: Ошибка байтового порядка

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

Обратите внимание на фразу "порядок байтов компьютера". Что имеет значение, это порядок байтов периферийного или кодированного потока данных, но - и это ключевой момент - порядок байтов компьютера, выполняющего обработку, не имеет отношения к обработке самих данных. Если поток данных кодирует значения с байтовым порядком B, то алгоритм для декодирования значения на компьютере с байтовым порядком C должен быть примерно B, а не соотношением между B и C.

Ответ 2

Единственный раз, когда вы должны заботиться о контенте, - это когда вы передаете двоичные данные, чувствительные к сущности (то есть, а не текст) между системами, которые могут не иметь одинаковую сущность. Обычным решением является использование " порядок байтов сети" (AKA big-endian) для передачи данных, а затем swizzle байты, если необходимо, на другой конец.

Для преобразования из хоста в сетевой порядок байтов используйте htons(3) и htonl(3). Для возврата назад используйте ntohl(3) и ntohs(3). Ознакомьтесь с справочной страницей для всего, что вам нужно знать. Для 64-битных данных этот вопрос и ответ будут полезны.

Ответ 3

Что я должен отслеживать при реализации приложения, которое я хочу быть агентом endian-agnostic?

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

В таких случаях значение endianess имеет значение целых чисел больше байта, так как целые числа по-разному представлены в памяти разными платформами. Это означает, что каждый раз, когда вам нужно читать или записывать внешние данные, вам нужно сделать больше, чем просто сбросить память вашей программы или прочитать данные непосредственно в свои собственные переменные.

например. если у вас есть этот фрагмент кода:

unsigned int var = ...;
write(fd, &var, sizeof var);

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

Если вы напишете эти данные в файл, содержимое файла будет отличаться от того, запускаете ли вы программу на большом концевом или маленьком концевом компьютере. Таким образом, код не является атрибутом endian agnostic, и вы бы хотели избежать подобных действий.

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

Как только вы знаете формат данных, а не, например, сбрасывая внутреннюю переменную напрямую, ваш код делает это:

uint32_t i = ...;
uint8_t buf[4];
buf[0] = (i&0xff000000) >> 24;
buf[1] = (i&0x00ff0000) >> 16;
buf[2] = (i&0x0000ff00) >> 8;
buf[3] = (i&0x000000ff);
write(fd, buf, sizeof buf);

Теперь мы выбрали самый старший байт и поместили его в качестве первого байта в буфер, а наименее значимый байт помещен в конец буфера. Это целое число представлено в формате большого конца в buf, вне зависимости от конечного узла хоста, поэтому этот код не является агентом endian.

Потребитель этих данных должен знать, что данные представлены в формате большого конца. И независимо от хоста программа работает, этот код будет читать эти данные просто отлично:

uint32_t i;
uint8_t buf[4];
read(fd, buf, sizeof buf);
i  = (uint32_t)buf[0] << 24;
i |= (uint32_t)buf[1] << 16;
i |= (uint32_t)buf[2] << 8;
i |= (uint32_t)buf[3];

И наоборот, если данные, которые вам нужно читать, как известно, имеют малоформатный формат, агностический код endianess просто сделает

uint32_t i ;
uint8_t buf[4];
read(fd, buf, sizeof buf);
i  = (uint32_t)buf[3] << 24;
i |= (uint32_t)buf[2] << 16;
i |= (uint32_t)buf[1] << 8;
i |= (uint32_t)buf[0];

Вы можете сделать несколько полезных встроенных функций или макросов для обертывания и разворачивания всех 2,4,8 байтовых целочисленных типов, которые вам нужны, и если вы используете их и заботитесь о формате данных, а не о конце процессора, на котором вы работаете, ваш код не будет зависеть от того, что он работает.

Это больше кода, чем многие другие решения. Мне еще предстоит написать программу, в которой эта дополнительная работа оказала какое-либо значимое влияние на производительность, даже когда shuffeling 1Gbps + данных вокруг.

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

uint32_t i;
uint8_t buf[4];
read(fd, buf, sizeof buf);
i = ntohl(*(uint32_t)buf));

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

Ответ 4

В нескольких ответах рассмотрен файл IO, который, безусловно, является наиболее распространенным вопросом. Я коснусь одного еще не упомянутого: Unions.

Следующее объединение является обычным инструментом программирования SIMD/SSE и не является дружественным для пользователя:

union uint128_t {
    _m128i      dq;
    uint64_t    dd[2];
    uint32_t    dw[4];
    uint16_t    dh[8];
    uint8_t     db[16];
};

Любой код, обращающийся к формам dd/dw/dh/db, будет делать это по-определенному. На 32-битных процессорах также довольно часто встречаются более простые объединения, которые позволяют более легко разбить 64-разрядную арифметику на 32-битные порции:

union u64_parts {
    uint64_t    dd;
    uint32_t    dw[2];
};

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

union u64_parts {
    uint64_t dd;
    struct {
#ifdef BIG_ENDIAN
        uint32_t dw2, dw1;
#else
        uint32_t dw1, dw2;
#endif
    }
};

В результате подразумевается неявное кодирование по умолчанию для любого кода, который напрямую обращается к dw1/dw2. Такой же подход к проектированию можно использовать и для 128-битного типа данных SIMD, хотя он становится значительно более подробным.

Отказ от ответственности: использование профсоюзов часто не одобряется из-за слабых определений стандартов в отношении заполнения и выравнивания структуры. Я считаю, что профсоюзы очень полезны и широко использовали их, и я не сталкивался с проблемами перекрестной совместимости в течение очень долгого времени (15+ лет). Соединение/выравнивание соединения будет вести себя ожидаемым и последовательным образом для любого текущего таргетинга на компилятор x86, ARM или PowerPC.

Ответ 5

Внутри вашего кода вы можете в значительной степени игнорировать его - все отменяется.

При чтении/записи данных на диск или в сети используйте htons

Ответ 6

Это явно спорный вопрос.

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

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

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

Другой альтернативой является только потребление и получение данных в текстовом формате. Это, вероятно, почти так же легко реализовать, и если у вас действительно высокий уровень данных в/из приложения с очень небольшой обработкой, то, вероятно, очень мало различий в производительности. И с выгодой (для некоторых), что вы можете читать входные и выходные данные в текстовом редакторе, а не пытаться декодировать, какое значение байтов 51213498-51213501 на самом деле должно быть, когда у вас что-то не так в код.

Ответ 7

Если вам нужно переинтерпретировать между типом целого числа 2,4 или 8 байтов и байт-индексированным массивом (или наоборот), вам нужно знать достоверность.

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

Обычно он обнаруживается макросом вроде ENDIAN... что-то.

Например:

uint32 x = ...;
uint8* p = (uint8*) &x;

p указывает на старший байт на машинах BE и младший байт на машине LE.

Используя макросы, вы можете написать:

uint32 x = ...;

#ifdef LITTLE_ENDIAN
    uint8* p = (uint8*) &x + 3;
#else // BIG_ENDIAN
    uint8* p = (uint8*) &x;
#endif

чтобы всегда получать старший байт.

Есть способы определить макрос здесь: C Определение макроса для определения большой конечной или маленькой конечной машины?, если ваша toolchain не предоставляет их.