Реверсивный порядок байтов в .NET.

В приведенном ниже коде, почему 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 GHz LGA 775 95W Quad-Core Processor Модель 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

Ответы

Ответ 1

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

BinaryReader считывает этот тип данных в формате little-endian.

Фактически, мы можем проверить источник для 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 читает как малое 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. В случае с маленьким концом буфер интерпретируется как маленький endian (так что память { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07 } интерпретируется как long 0x0706050403020100), а в случае большого endian буфер интерпретируется как большой endian (так что память { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07 } интерпретируется как длинный 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, и поэтому поведение, которое вы видите, является правильным.

Ответ 2

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

Ответ 4

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

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

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

Ответ 5

Если вы заботитесь о контенте ваших байтов, Jon Skeet написал класс, чтобы вы могли выбрать конечный порядок при выполнении преобразования.

Смотрите С# little endian или big endian?

Ответ 6

Это просто:

if (BitConverter.IsLittleEndian == true) Array.Reverse(var);

Ответ 7

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

IPAddress.HostToNetworkOrder(BitConverter.ToInt64(buffer))