Преамбула
Известно, что любые данные хранятся в памяти компьютера в виде последовательности байт, байт обычно равен 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
будет «четырехбайтовым», и в случае, если оно записывается в порядке big—endian, то в памяти оно будет храниться в обычном для нас виде, как последовательность 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