C#, о порядке байт, little-endian, big-endian

Преамбула

Известно, что любые данные хранятся в памяти компьютера в виде последовательности байт, байт обычно равен 8 битам (но бывают и исключения, о которых сейчас не будем). Числа не исключение, например, когда мы определяем переменную int, фактически мы говорим компилятору «выдай нам 4 байта по такому-то адресу в памяти, для записи числа от -2147483648 до 2147483647». Конкретный адрес, конечно, заботливо выбирает для нас среда .NET, и обычно, лезть туда руками не нужно, среда все прекрасно за нас сделает. Пока не случается какой-нибудь хитрый случай, и тут уже приходится думать самому. Когда работаешь с однобайтовыми числами, обычно никаких чудес не происходит — байт он и на десктопной машине с Windows или Linux байт, и на сервере байт, и даже на роутере, телефоне и утюге, скорее всего будет тем же самым байтом. Но все было бы хорошо, если бы одного байта хватало всем 🙂
Как уже сказано выше, тот же int, это не один байт, а 4, и вот тут кроется проблема. Эти самые байты можно хранить в памяти как угодно, хоть через один, хоть в шахматном порядке.
На наше счастье, такие извращения, если и встречаются, то очень редко. В большинстве вычислительных устройств байты (одного числа, например типа int), хранятся либо в последовательности от старшего к младшему big-endian, либо от младшего к старшему little-endian. В случае big-endian первым идет старший разряд числа, а остальные по ранжиру за ним, в случае ltiite-endian — наоборот, первым идет самый младший, последним — самый старший.

Чтобы было проще представить, предположим, что в 1 байт влезает не десятичное число от 0 до 255 (256 значений), а всего 10 значений, т.е. числа от 0 до 9.
В таком случае число 1234 будет «четырехбайтовым», и в случае, если оно записывается в порядке bigendian, то в памяти оно будет храниться в обычном для нас виде, как последовательность 1 2 3 4, поэтому, порядок big-endian еще называют прямым порядком байт. Если же число хранится в порядке little-endian, то в памяти оно будет выглядеть, как последовательность 4 3 2 1, поэтому порядок little-endian называют обратным порядком байт.

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

Итак:
Big-endian — «от старшего к младшему», он же прямой, сетевой (поскольку принят в качестве стандартного порядка байт при передаче данных по сети), Motorola byte order (использовался в процессорах Motorola, а не в честь уехавшего на социальном лифте деятеля).
В big-endian формате хранятся IP-адреса.
Little-endian — «От младшего к старшему», обратный, интеловский (используется в процессорах Intel), VAX (использовался на платформе VAX).

Маленькая иллюстрация

Заведем переменную типа int, содержащую число 16909060 и два массива байт byte[] LittleEndian и byte[] BigEndian, содержащие его представление в виде последовательности байт в обратном и прямом порядке:

int Constant = 16909060;
byte[] LittleEndian = new byte[] {4, 3, 2, 1};
byte[] BigEndian = new byte[] {1, 2, 3, 4 };

Да, число подобрано специально, чтобы было красивое представление его в массиве байт. 🙂

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

BitConverter.ToInt32(LittleEndian,0); //результат - 16909060
BitConverter.ToInt32(BigEndian, 0); //результат - 67305985

Класс BitConverter в теории должен использовать при преобразовании тот порядок байт, который используется на данной машине, соответственно, правильное число 16909060 было получено при преобразовании массива LittleEndian.

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

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

Средствами .NET

Для того, чтобы средствами .NET узнать, какой порядок байт используется на машине, где выполняется ваша программа, можно посмотреть в переменную IsLittleEndian класса BitConverter. Если она принимает значение true, то используется, соответственно, порядок little-endian (обратный):

if (BitConverter.IsLittleEndian)
{
    Console.WriteLine("little-endian");
}
else
{
    Console.WriteLine("most likely big-endian");
}

Самостоятельно

Легко написать функцию проверки самому. Достаточно взять некое заранее известное число, его представление в виде big-endian и little-endian в виде массива байт, сконвертировать массивы обратно в число, и сравнить с заранее известным.

Создадим перечисление ByteOrder(для красоты :):

private enum ByteOrder
{
    BigEndian = 0,
    LittleEndian = 1,
    Unknow = 3
}

И напишем функцию:

ByteOrder DetectBO()
{    
    int Constant = 16909060;
    byte[] LittleEndian = new byte[] { 4, 3, 2, 1 };
    byte[] BigEndian = new byte[] { 1, 2, 3, 4 };

    if (BitConverter.ToInt32(BigEndian, 0) == Constant) 
        return ByteOrder.BigEndian;
    if (BitConverter.ToInt32(LittleEndian, 0) == Constant) 
        return ByteOrder.LittleEndian;
    
    return ByteOrder.Unknow;
}

Примечание: в тексте в кодировке UTF-16 можно определить UTF-16LE или UTF-16BE при помощи BOM, при его наличии

Преобразование из big-endian в little-endian и наоборот.

А вот тут у .NET Framework’а все как-то печально, частично могут помочь статические функции класса System.Net.IPAddress NetworkToHostOrder() и HostToNetworkOrder(), преобразующие, соответственно, число, полученное в big-endian в формат, используемый на данной машине и наоборот, но они довольно ограничены, поддерживают только long, int и short значения, не поддерживают работу с беззнаковыми числами и числами с плавающей запятой, а также с массивами байт. Вот способ преобразования порядка байт:

1. Преобразовать исходное число в массив байт с помощью BitConverter.GetBytes() или взять готовый массив, если он есть.
2. Перевернуть массив функцией Array.Reverse()
3. Преобразовать развернутый массив обратно, с помощью одной из функций класса BitConverter:

byte [] ConvArray = BitConverter.GetBytes(BigEndianValue);
Array.Reverse(ConvArray);
ushort LittleEndianValue = BitConverter.ToUInt16(ConvArray,0);

Минус — относительно медленная функция Array.Reverse. И BitConverter тоже не самый быстрый класс, зато довольно наглядно.

Внимание! Функция Array.Reverse() не создает копии массива, а работает с указанным массивом прямо в памяти. Если в функцию, в которой используется Array.Reverse() будет передан массив из вызывающей подпрограммы, и функция Array.Reverse() будет к массиву применена, то массив изменится. Такое поведение может породить труднообнаруживаемую ошибку, поэтому, если массив в оригинальном виде планируется еще где-либо использовать, то перед Array.Reverse() надо сделать его копию с помощью Array.Copy().

Источники

1. Порядок байтов
2. Разбираемся с прямым и обратным порядком байтов
3. MSDN

Пример к заметке на GitHub

One Response to C#, о порядке байт, little-endian, big-endian

  1. Pingback: C#, проверка корректности и преобразования IP-адреса, класс IPAddress | Персональный блог Толика Панкова

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *