Реверсивный порядок байтов в .NET - PullRequest
13 голосов
/ 05 января 2010

В приведенном ниже коде почему X и Y принимают значения, отличные от того, что я думаю интуитивно?

Если байты 0-7 записываются в буфер, не должны ли полученные длинные байты иметь одинаковый порядок? Это похоже на чтение длинных значений в обратном порядке.

x    0x0706050403020100    long
y    0x0706050403020100    long
z    0x0001020304050607    long

MemoryStream ms = new MemoryStream();
byte[] buffer = new byte[] { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07 };
ms.Write(buffer, 0, buffer.Length);
ms.Flush();
ms.Position = 0;

BinaryReader reader = new BinaryReader(ms);
long x = reader.ReadInt64();
long y = BitConverter.ToInt64(buffer, 0);
long z = BitConverter.ToInt64(buffer.Reverse<byte>().ToArray<byte>(), 0);

byte[] xbytes = BitConverter.GetBytes(x);
byte[] ybytes = BitConverter.GetBytes(y);
byte[] zbytes = BitConverter.GetBytes(z);

(я не знаю, что пометить этим вопросом, кроме .NET.)


BitConverter.IsLittleEndian

ложно. Если мой компьютер с прямым порядком байтов, почему это происходит?

  • Это 64-разрядный компьютер под управлением Windows 7
  • Четырехъядерный процессор Intel Core2 Quad Q9400 с частотой 2,66 ГГц LGA 775, 95 Вт, модель BX80580Q9400
  • SUPERMICRO MBD-C2SBX + -O LGA 775 Intel X48 ATX Материнская плата Intel

Результаты этого кода (в ответ на комментарий Джейсона):

byte[] buffer = new byte[] { 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07 };
long y = BitConverter.ToInt64(buffer, 1);
Console.WriteLine(BitConverter.IsLittleEndian);
Console.WriteLine(y);

Результат:

False
506097522914230528

Ответы [ 7 ]

21 голосов
/ 07 января 2010

BinaryReader.ReadInt64 по порядку имеет младший порядок. Из документации:

BinaryReader читает этот тип данных в формате с прямым порядком байтов.

Фактически, мы можем проверить источник на BinaryReader.ReadInt64, используя Reflector.

public virtual long ReadInt64() {
    this.FillBuffer(8);
    uint num = (uint) (((this.m_buffer[0] |
              (this.m_buffer[1] << 0x08)) |
              (this.m_buffer[2] << 0x10)) |
              (this.m_buffer[3] << 0x18));
    uint num2 = (uint) (((this.m_buffer[4] |
               (this.m_buffer[5] << 0x08)) |
               (this.m_buffer[6] << 0x10)) |
               (this.m_buffer[7] << 0x18));
    return (long) ((num2 << 0x20) | num);
}

Показывает, что BinaryReader.ReadInt64 читается как little-endian независимо от базовой архитектуры машины.

Теперь, BitConverter.ToInt64 предполагается, чтобы уважать порядковый номер вашей базовой машины. В Reflector мы видим

public static unsafe long ToInt64(byte[] value, int startIndex) {
    // argument checking elided
    fixed (byte* numRef = &(value[startIndex])) {
        if ((startIndex % 8) == 0) {
            return *(((long*) numRef));
        }
        if (IsLittleEndian) {
            int num = (numRef[0] << 0x00) |
                      (numRef[1] << 0x08) |
                      (numRef[2] << 0x10) |
                      (numRef[3] << 0x18);
            int num2 = (numRef[4] << 0x00) |
                       (numRef[5] << 0x08) |
                       (numRef[6] << 0x10) |
                       (numRef[7] << 0x18);
            return (((long) ((ulong) num)) | (num2 << 0x20));
        }
        int num3 = (numRef[0] << 0x18) |
                   (numRef[1] << 0x10) |
                   (numRef[2] << 0x08) |
                   (numRef[3] << 0x00);
        int num4 = (numRef[4] << 0x18) |
                   (numRef[5] << 0x10) |
                   (numRef[6] << 0x08) |
                   (numRef[7] << 0x00);
        return (((long) ((ulong) num4)) | (num3 << 0x20));
}

Итак, мы видим, что если startIndex соответствует нулю по модулю восемь, то прямое преобразование выполняется из восьми байтов, начиная с адреса numRef. Этот случай обрабатывается специально из-за проблем с выравниванием. Строка кода

return *(((long *) numRef));

переводится непосредственно в

    ldloc.0      ;pushes local 0 on stack, this is numRef
    conv.i       ;pop top of stack, convert to native int, push onto stack
    ldind.i8     ;pop address off stack, indirect load from address as long
    ret          ;return to caller, return value is top of stack

Итак, мы видим, что в этом случае ключом является инструкция ldind.i8. CLI не зависит от порядка базовой машины. Это позволяет JIT-компилятору справиться с этой проблемой. На машине с прямым порядком байтов ldind.i8 загрузит старшие адреса в более значимые биты, а на машине с прямым порядком байтов ldind.i8 загрузит старшие адреса в менее значимые байты. Следовательно, в этом случае порядок байтов обрабатывается правильно.

В другом случае вы можете видеть, что есть явная проверка статического свойства BitConverter.IsLittleEndian. В случае с прямым порядком байтов буфер интерпретируется как младший (так что память { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07 } интерпретируется как длинный 0x0706050403020100), а в случае с большим порядком байтов буфер интерпретируется как большой (так что память интерпретируется как длинный 0x0001020304050607). Таким образом, для BitConverter все сводится к порядку работы машины-подложки. Хочу заметить, что вы используете чип Intel на Windows 7 x64. Чипы Intel имеют младший порядок. Я отмечаю, что в Reflector статический конструктор для BitConverter определяется следующим образом:

static BitConverter() {
    IsLittleEndian = true;
}

Это на моем компьютере с Windows Vista x64. (Это может отличаться, скажем, от .NET CF на XBox 360.) Нет никаких причин для Windows 7 x64 быть другой. Следовательно, вы уверены, что BitConverter.IsLittleEndian - это false? Это должно быть true, и поэтому поведение, которое вы видите, является правильным.

5 голосов
/ 05 января 2010

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

4 голосов
/ 05 января 2010

BinaryReader предполагает порядок Little Endian: http://msdn.microsoft.com/en-us/library/system.io.binaryreader.readint64.aspx

3 голосов
/ 27 мая 2011

Вы ПОЛНОСТЬЮ уверены, что BitConverter.IsLittleEndian возвращает false?

Если вы проверите его через часы отладчика, прежде чем использовать какой-либо из его методов, вы можете получить значение false, даже если оно должно вернуть true.

Считайте значение через код, чтобы быть полностью уверенным. См. Также Поле IsLittleEndian сообщает о ложном значении, но оно должно быть Little-Endian?

2 голосов
/ 16 мая 2013

Это просто:

if (BitConverter.IsLittleEndian == true) Array.Reverse(var);
2 голосов
/ 05 января 2010

Если вы заботитесь о порядке байтов в байтах, Джон Скит написал класс, позволяющий вам выбирать порядок байтов при выполнении преобразования.

См. C # порядковый номер или порядковый номер?

1 голос
/ 30 апреля 2015

BitConverter использует порядковый номер машины, на которой он работает. Чтобы обеспечить порядковый номер, используйте IPAddress.HostToNetworkOrder. Например:

IPAddress.HostToNetworkOrder(BitConverter.ToInt64(buffer))
...