Порядок байтов
В современной вычислительной технике и цифровых системах связи информация обычно представлена в виде последовательности байтов. В том случае, если число не может быть представлено одним байтом, имеет значение в каком порядке байты записываются в памяти компьютера или передаются по линиям связи. Часто выбор порядка записи байтов произволен и определяется только соглашениями.
В общем случае, для представления числа M, большего 255 (здесь — максимальное целое число, записываемое одним байтом), приходится использовать несколько байтов. При этом число M записывается в позиционной системе счисления по основанию 256:
Набор целых чисел , каждое из которых лежит в интервале от 0 до 255, является последовательностью байтов, составляющих M. При этом
называется младшим байтом, а
— старшим байтом числа M.
Варианты записи
Порядок от старшего к младшему
Порядок от старшего к младшему или (англ. big-endian, дословно: «тупоконечный»): , запись начинается со старшего и заканчивается младшим. Этот порядок является стандартным для протоколов TCP/IP, он используется в заголовках пакетов данных и во многих протоколах более высокого уровня, разработанных для использования поверх TCP/IP. Поэтому, порядок байтов от старшего к младшему часто называют сетевым порядком байтов (англ. network byte order). Этот порядок байтов используется процессорами IBM 360/370/390, Motorola 68000, SPARC (отсюда третье название — порядок байтов Motorola, Motorola byte order).
В этом же виде (используя представление в десятичной системе счисления) записываются числа индийско-арабскими цифрами в письменностях с порядком знаков слева направо (латиница, кириллица). Для письменностей с обратным порядком (арабская) та же запись числа воспринимается как «от младшего к старшему».
Порядок байтов от старшего к младшему применяется во многих форматах файлов — например, PNG, FLV, EBML.
Порядок от младшего к старшему
Порядок от младшего к старшему или (англ. little-endian, дословно: «остроконечный», о происхождении термина ниже): , запись начинается с младшего и заканчивается старшим. Этот порядок записи принят в памяти персональных компьютеров с x86-процессорами, в связи с чем иногда его называют интеловский порядок байт (по названию фирмы-создателя архитектуры x86). Процессоры x86 позволяют работать с одно-, двух-, четырёх-, восьмибайтными операндами. При изменении размера операнда в памяти адрес не изменяется при таком порядке байт. В некоторых кругах используется название англ. VAX order, например, в документации Perl1).
В противоположность порядку «от старшего к младшему» (англ. big-endian, «тупоконечный»), соглашение little-endian поддерживают меньше кросс-платформенных протоколов и форматов данных; существенные исключения: USB, конфигурация PCI, таблица разделов GUID, рекомендации FidoNet.
Переключаемый порядок
Многие процессоры могут работать и в порядке от младшего к старшему, и в обратном, например, ARM, PowerPC (но не PowerPC 970), DEC Alpha, MIPS, PA-RISC и IA-64. Обычно порядок байтов выбирается программно во время инициализации операционной системы, но может быть выбран и аппаратно перемычками на материнской плате. В этом случае правильнее говорить о порядке байтов операционной системы. Переключаемый порядок байтов иногда называют англ. bi-endian.
Смешанный порядок
Смешанный порядок байтов (англ. middle-endian) иногда используется при работе с числами, длина которых превышает машинное слово. Число представляется последовательностью машинных слов, которые записываются в формате, естественном для данной архитектуры, но сами слова следуют в обратном порядке.
В процессорах VAX и ARM используется смешанное представление для длинных вещественных чисел.
Пример
Далее приведён пример, в котором описывается размещение 4-байтового числа в ОЗУ ЭВМ, доступ к которому может производиться и как к 32-разрядному слову, и побайтно.
Все числа записаны в 16-ричной системе счисления.
Представление | D4*0x01 + C3*0x100 + B2*0x10000 + A1*0x1000000 | |
Порядок от младшего к старшему | (little-endian) | 0xD4, 0xC3, 0xB2, 0xA1 |
Порядок от старшего к младшему | (big-endian) | 0xA1, 0xB2, 0xC3, 0xD4 |
Порядок, принятый в PDP-11 | (PDP-endian) | 0xB2, 0xA1, 0xD4, 0xC3 |
Сравнение
Существенным достоинством little-endian по сравнению с big-endian порядком записи считается возможность «неявной типизации» целых чисел при чтении меньшего объёма байт (при условии, что читаемое число помещается в диапазон). Так, если в ячейке памяти содержится число 0x00000022, то прочитав его как int16 (два байта) мы получим число 0x0022, прочитав один байт — число 0x22. Однако, это же может считаться и недостатком, потому что провоцирует ошибки потери данных.
Обратно, считается что у little-endian, по сравнению с big-endian есть «неочевидность» значения байтов памяти при отладке (последовательность байтов (A1, B2, C3, D4) на самом деле значит 0xD4C3B2A1, для big-endian эта последовательность (A1, B2, C3, D4) читалась бы «естественным» для арабской записи чисел образом: 0xA1B2C3D4). Наименее удобным в работе считается middle-endian формат записи; он сохранился только на старых платформах.
Для записи длинных чисел (чисел, длина которых существенно превышает разрядность машины) обычно предпочтительнее порядок слов в числе little-endian (поскольку арифметические операции над длинными числами производятся от младших разрядов к старшим). Порядок байтов в слове — обычный для данной архитектуры.
Определение порядка байтов
Порядок байтов в конкретной машине можно определить с помощью программы на языке Си (testbyteorder.c):
#include <stdio.h> unsigned short x = 1; /* 0x0001 */ int main(void) { printf("%s\n", *((unsigned char *) &x) == 0 ? "big-endian" : "little-endian"); return 0; }
- Вывод данной программы осмыслен только на платформах, где размер типа
unsigned short
больше, чем размер типаunsigned char
. Это заведомо верно на подавляющем большинстве компьютеров, так как они имеют 8-разрядный байт. Однако существуют и аппаратные платформы, в которых размер байта равен размеру слова (или, в терминах языка C:sizeof(char) == sizeof(short)
). Например, в суперкомпьютерах Cray.
Результаты запуска на big-endian машине (SPARC):
$ uname -m sparc64 $ gcc -o testbyteorder testbyteorder.c $ ./testbyteorder big-endian
Результаты запуска на little-endian машине (x86):
$ uname -m i386 $ gcc -o testbyteorder testbyteorder.c $ ./testbyteorder little-endian
Вещественные числа
Хранение вещественных чисел может зависеть от порядка байт; так, на x86 используются форматы IEEE 754 со знаком и порядком числа в старших байтах.
Юникод
Если Юникод записан в виде UTF-16 или UTF-32, то порядок байтов является существенным. Одним из способов обозначения порядка байтов в юникодовых текстах является постановка в начале специального символа BOM (byte order mark, маркер последовательности байтов, U+FEFF) — «перевёрнутый» вариант этого символа (U+FFFE) не существует и не допускается в текстах.
Символ U+FEFF изображается в UTF-16 последовательностью байтов 0xFE 0xFF (big-endian) или 0xFF 0xFE (little-endian), а в UTF-32 — последовательностью 0x00 0x00 0xFE 0xFF (big-endian) или 0xFF 0xFE 0x00 0x00 (little-endian).
Проблемы совместимости и конвертация
Запись многобайтового числа из памяти компьютера в файл или передача по сети требует соблюдения соглашений о том, какой из байтов передается первым. Прямая запись в том порядке, в котором байты расположены в ячейках памяти приводит к проблемам при переносе приложения с платформы на платформу.
Для преобразования между сетевым порядком байтов (англ. network byte order), который всегда big-endian, и порядком байтов, использующимся на машине (англ. host byte order), стандарт POSIX предусматривает функции htonl()
, htons()
, ntohl()
, ntohs()
:
uint32_t htonl(uint32_t hostlong);
— конвертирует 32-битную беззнаковую величину из локального порядка байтов в сетевой;uint16_t htons(uint16_t hostshort);
— конвертирует 16-битную беззнаковую величину из локального порядка байтов в сетевой;uint32_t ntohl(uint32_t netlong);
— конвертирует 32-битную беззнаковую величину из сетевого порядка байтов в локальный;uint16_t ntohs(uint16_t netshort);
— конвертирует 16-битную беззнаковую величину из сетевого порядка байтов в локальный.
В случае совпадения текущего порядка байтов и сетевого, функции могут быть «пустыми» (то есть, не менять порядка байтов). Стандарт также допускает, чтобы эти функции были реализованы макросами.
Существует много языков и библиотек со средствами конвертации в оба основных порядка байт и обратно.
Ядро Linux: le16_to_cpu(), cpu_to_be32(), cpu_to_le16p(), и так далее;
Ядро FreeBSD: htobe16(), le32toh(), и так далее;
<<Count:32/big-unsigned-integer, Average:64/big-float>> = Chunk Message = <<Length:32/little-unsigned-integer, MType:16/little-unsigned-integer, MessageBody>>
import struct Count, Average = struct.unpack(">Ld", Chunk) Message = struct.pack("<LH", Length, MType) + MessageBody
Perl:
($Count, $Average) = unpack('L>d>', $Chunk); $Message = pack('(LS)<', $Length, $MType) . $MessageBody; (или то же самое: $Message = pack('Vv', $Length, $MType) . $MessageBody;)
данные примеры для Erlang, Python, Perl содержат идентичную функциональность.
Процессоры Intel x86-64 имеют инструкцию BSWAP для смены порядка байт.
Этимология названия
Термины big-endian и little-endian первоначально не имели отношения к информатике. В сатирическом произведении Джонатана Свифта «Путешествия Гулливера» описываются вымышленные государства Лилипутия и Блефуску, в течение многих лет ведущие между собой войны из-за разногласия по поводу того, с какого конца следует разбивать варёные яйца. Тех, кто считает, что их нужно разбивать с тупого конца, в произведении называют Big-endians («тупоконечники»). Споры между сторонниками big-endian и little-endian в информатике также часто носят характер т. н. «религиозных войн».2)
Термины big-endian и little-endian ввёл Коэн (англ. Danny Cohen) в 1980 году в своей статье «On Holy Wars and a Plea for Peace».3)4)