【问题标题】:Reversing byte order in .NET在 .NET 中反转字节顺序
【发布时间】:2010-01-05 00:32:36
【问题描述】:

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

(除了 .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

【问题讨论】:

  • 添加标签供您搜索:)
  • 你能提供机器的规格(处理器、mb、O/S)吗?
  • 这是 Intel Core 2,Windows 7。我还应该补充一点,BitConverter.IsLittleEndian 返回 false。
  • 请您执行以下操作:byte[] buffer = new byte[] { 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07 }; long y = BitConverter.ToInt64(buffer, 1); 并报告y 的值。请注意,buffer 比您给出的定义多了一个字节。我无法访问BitConverter.IsLittleEndian 为假的机器。
  • Jason:我在票的编辑 2 中回答了你的问题。

标签: .net endianness


【解决方案1】:

BinaryReader.ReadInt64 设计为小端。来自文档:

BinaryReader 以 little-endian 格式读取此数据类型。

实际上,我们可以使用 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 与零模八一致,则直接转换是从地址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 编译器处理这个问题。在 little-endian 机器上,ldind.i8 会将较高的地址加载到更高的有效位中,而在 big-endian 的机器上,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 上的英特尔芯片。英特尔芯片是小端的。我注意到在 Reflector 中,BitConverter 的静态构造函数定义如下:

static BitConverter() {
    IsLittleEndian = true;
}

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

【讨论】:

  • 这是我在 stackoverflow 上找到的最有用的答案。感谢您一直深入了解 IL 指令的行为。对于为什么在显式字节序更正发生之前某段代码正在工作,我感到非常困惑。
  • 那么有没有办法强制反转读取数组(发出前导零,反转后变成尾随零)。
  • 鉴于字节通常来自具有定义规格的文件,我不明白为什么这些转换必须依赖于机器的字节序而不是,你知道的,布尔参数......
【解决方案2】:

您在little endian 机器上,其中整数首先存储最低有效字节。

【讨论】:

  • 作为一个非.NET 的人,我对这个问题感到惊讶。我本来希望 .NET 至少像 Java 一样使程序员免受字节序的影响。
  • 机器的字节顺序无关紧要,CLI 假定为小字节序。
  • 它没有。 Java 也没有。反转字节顺序方式太昂贵了。这几天没什么大问题,小端赢了。
  • BitConverter.IsLittleEndian 返回 false
【解决方案3】:

【讨论】:

    【解决方案4】:

    您是否完全确定 BitConverter.IsLittleEndian 返回 false?

    如果你在使用它的任何方法之前通过调试器监视检查它,即使它应该返回 true,你也可能会得到 false。

    通过代码读出值完全确定。 另见IsLittleEndian field reports false, but it must be Little-Endian?

    【讨论】:

      【解决方案5】:

      如果您关心字节的字节序,Jon Skeet 编写了一个类,允许您在进行转换时选择字节序。

      C# little endian or big endian?

      【讨论】:

        【解决方案6】:

        只是:

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

        【讨论】:

        • 为什么这篇文章有帮助吗?
        • 我也很好奇这个。这段代码似乎解决了我遇到的问题,但我想确定它的实际作用。
        【解决方案7】:

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

        IPAddress.HostToNetworkOrder(BitConverter.ToInt64(buffer))
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2021-11-17
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-04-28
          相关资源
          最近更新 更多