Как написать код на C / C ++, не зависящий от порядка байтов?

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

33

Решение

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

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

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

13

Другие решения

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

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

28

На что следует обращать внимание при реализации приложения, которое я хочу быть независимым от порядка байтов?

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

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

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

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

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

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

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

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

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независимо от порядкового номера хоста — поэтому этот код не зависит от порядкового номера.

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

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];

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

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-байтовые целочисленные типы, и если вы используете их и заботитесь о формате данных, а не о порядке байтов процессора, на котором вы работаете, ваш код будет не зависит от порядка, на котором он работает.

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

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

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

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

16

Несколько ответов были посвящены файлу ввода-вывода, что, безусловно, является наиболее распространенной проблемой порядка байтов. Я коснусь одного еще не упомянутого: Союзы.

Следующий союз является распространенным инструментом в программировании 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
}
};

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

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

8

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

Когда вы читаете / записываете данные на диск или в сеть используете htons

2

Это явно довольно спорная тема.

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

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

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

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

1

Если вам нужно переинтерпретировать между целочисленным типом 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 Макроопределение для определения машины с прямым или прямым порядком байтов? если ваш инструментарий не предоставляет их.

0
По вопросам рекламы ammmcru@yandex.ru
Adblock
detector