在.NET中反转字节顺序

Amy*_*Amy 13 .net endianness

在下面的代码中,为什么X和Y采用的不同于我想象的直观值?

如果将字节0-7写入缓冲区,那么得到的long不应该具有相同顺序的字节吗?这就像它以相反的顺序读取长值.

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);
Run Code Online (Sandbox Code Playgroud)

(除了.NET之外,我不知道要标记这个问题的内容.)


BitConverter.IsLittleEndian
Run Code Online (Sandbox Code Playgroud)

是假的.如果我的电脑是大端,为什么会这样?

  • 这是一台Windows 7 64位计算机
  • Intel Core2 Quad Q9400 2.66 GHz LGA 775 95W四核处理器型号BX80580Q9400
  • SUPERMICRO MBD-C2SBX + -O LGA 775 Intel X48 ATX Intel主板

这段代码的结果(回应Jason的评论):

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);
Run Code Online (Sandbox Code Playgroud)

结果:

False
506097522914230528
Run Code Online (Sandbox Code Playgroud)

jas*_*son 22

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);
}
Run Code Online (Sandbox Code Playgroud)

显示该BinaryReader.ReadInt64读取为独立于底层机器架构的小端.

现在,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));
}
Run Code Online (Sandbox Code Playgroud)

所以我们在这里看到的是,如果startIndex对于零模8是一致的,那么从地址开始的8个字节完成直接转换numRef.由于对齐问题,特殊处理此案例.代码行

return *(((long *) numRef));
Run Code Online (Sandbox Code Playgroud)

直接翻译成

    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
Run Code Online (Sandbox Code Playgroud)

所以我们看到在这种情况下,关键是ldind.i8指令.CLI与底层机器的字节顺序无关.它让JIT编译器处理该问题.在小端机器上,ldind.i8将更高的地址加载到更重要的位和大端机器上ldind.i8上将更高的地址加载到不太重要的字节.因此,在这种情况下,正确处理字节顺序.

在另一种情况下,您可以看到显式检查静态属性BitConverter.IsLittleEndian.在little endian的情况下,缓冲区被解释为little endian(因此内存{ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07 }被解释为long 0x0706050403020100),而在big endian的情况下,缓冲区被解释为big endian(因此内存{ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07 }被解释为long 0x0001020304050607).因此,对于BitConverter这一切都归结为底层机器的字节序.我注意到你使用的是Windows 7 x64上的英特尔芯片.英特尔芯片是小端.我注意到在Reflector中,静态构造函数for BitConverter定义如下:

static BitConverter() {
    IsLittleEndian = true;
}
Run Code Online (Sandbox Code Playgroud)

这是在我的Windows Vista x64机器上.(例如,XBox 360上的.NET CF可能会有所不同.)Windows 7 x64没有任何理由不同.因此,你确定BitConverter.IsLittleEndianfalse吗?它应该是true,因此你看到的行为是正确的.

  • 这是我在stackoverflow上找到的最有用的答案.感谢您一直钻进IL指令的行为.在明确的字节序修正发生之前,为什么某段代码正常工作,我感到非常困惑. (2认同)

I. *_*edy 5

你在一个小端机器上,其中整数首先存储最不重要的字节.

  • 作为一个非.NET人,我对这个问题感到惊讶.我原以为.NET会让程序员至少像Java一样将字节序与字节序隔离开来. (2认同)
  • 它没有.Java也不是.反转字节顺序是*方式*太贵了.这些天来没有太大的问题,小端已经赢了. (2认同)