在.NET中反转字节顺序

16
在下面的代码中,为什么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

如果我的计算机是大端序,为什么会出现这种情况呢?

  • 这是一台运行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);

结果:

False
506097522914230528

1
添加了标签,方便您搜索 :) - spender
你能提供一下这台机器的规格吗?(处理器、内存、操作系统)? - GrayWizardx
这是一台Intel Core 2电脑,运行Windows 7操作系统。我还应该补充一点,即BitConverter.IsLittleEndian返回false。 - user47589
等等,你确定 BitConverter.IsLittleEndian 是 false 吗?Intel 机器都是小端字节序。 - jason
@Jason:我已经编辑了您最近的评论所涉及的票证。 IsLittleEndian 打印为 False,并且编码数字也已经为您打印出来了。 - user47589
显示剩余4条评论
7个回答

25

BinaryReader.ReadInt64被设计为小端序。根据文档:

BinaryReader以小端序格式读取此数据类型。

实际上,我们可以使用Reflector检查BinaryReader.ReadInt64的源代码。

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 读取的结果是小端字节序,与底层机器体系结构无关。

现在,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模8等于零,则会直接将从地址numRef开始的8个字节进行强制转换。由于对齐问题,这种情况被特殊处理。代码行

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),而在大端情况下,缓冲区被解释为大端(因此内存{0x00、0x01、0x02、0x03、0x04、0x05、0x06、0x07}被解释为长整型0x0001020304050607)。因此,对于BitConverter,所有的问题都归结为底层计算机的字节序。我注意到你的机器是Windows 7 x64上的Intel芯片,而Intel芯片是小端字节序的。我注意到,在Reflector中,BitConverter的静态构造函数定义如下:
static BitConverter() {
    IsLittleEndian = true;
}

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


2
这是我在stackoverflow上找到的最有用的答案。感谢您深入挖掘IL指令的行为。在进行显式端点校正之前,我绝对不知道为什么某段代码能够工作。 - Abtin Forouzandeh
那么有没有一种方法可以强制数组被反向读取(发出前导零,在反转后变成尾随零)? - Shimmy Weitzhandler
考虑到字节通常来自具有定义规范的文件,我从未理解为什么这些转换需要依赖于机器的字节序而不是一个布尔参数... - Nyerguds

6

您正在使用小端机器,其中整数按最不重要的字节优先存储。


2
作为一个非.NET人士,我对这个问题感到惊讶。我本来期望.NET至少能像Java一样隔离程序员与字节序的关系。 - Licky Lindsay
1
机器的字节顺序不重要,CLI 假定为小端序。 - mletterle
3
Java也不支持。反转字节顺序太过昂贵,在如今这个时代已经不是什么大问题了,小端模式胜出。 - Hans Passant
1
BitConverter.IsLittleEndian 返回 false。 - user47589


3

这只是:

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

这段代码为什么会有帮助,可以做出解释吗? - vyegorov
我也很好奇。这段代码似乎解决了我遇到的问题,但我想确保它实际上是做什么的。 - hbulens

3

2

1

BitConverter使用运行它的机器的字节序。为了确保大端序数字,请使用IPAddress.HostToNetworkOrder。例如:

IPAddress.HostToNetworkOrder(BitConverter.ToInt64(buffer))

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接