Reversing byte order in .NET

后端 未结 7 1126
天命终不由人
天命终不由人 2020-12-03 11:29

In the code below, why do X and Y take on different values than what I would think intuitively?

If the bytes 0-7 are written to the buffer, shouldn\'t the resulting

相关标签:
7条回答
  • 2020-12-03 11:56

    BitConverter uses the endianness of the machine it's running on. To ensure a big-endian number, use IPAddress.HostToNetworkOrder. For example:

    IPAddress.HostToNetworkOrder(BitConverter.ToInt64(buffer))
    
    0 讨论(0)
  • 2020-12-03 11:57

    BinaryReader.ReadInt64 is little endian by design. From the documentation:

    BinaryReader reads this data type in little-endian format.

    In fact, we can inspect the source for BinaryReader.ReadInt64 using 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);
    }
    

    Showing that BinaryReader.ReadInt64 reads as little endian independent of the underlying machine architecture.

    Now, BitConverter.ToInt64 is suppose to respect the endianness of your underlying machine. In Reflector we can see

    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));
    }
    

    So what we see here is that if startIndex is congruent to zero modulo eight that a direct cast is done from eight bytes starting at address numRef. This case is handled specially because of alignment issues. The line of code

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

    translates directly to

        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
    

    So we see that in this case the key is the ldind.i8 instruction. The CLI is agnostic about the endianness of the underlying machine. It lets the JIT compiler handle that issue. On a little-endian machine, ldind.i8 will load higher addresses into more significant bits and on a big-endian machine ldind.i8 will load higher addresses into less significant bytes. Therefore, in this case, endianness is handled properly.

    In the other case, you can see that there is an explicit check of the static property BitConverter.IsLittleEndian. In the case of little endian the buffer is interpreted as little endian (so that memory { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07 } is interpreted as the long 0x0706050403020100) and in case of big endian the buffer is interpreted as big endian (so that memory { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07 } is interpreted as the long 0x0001020304050607). So, for BitConverter it all comes down to the endianness of the underyling machine. I note that you're on an Intel chip on Windows 7 x64. Intel chips are little endian. I note that in Reflector, the static constructor for BitConverter is defined as the following:

    static BitConverter() {
        IsLittleEndian = true;
    }
    

    This is on my Windows Vista x64 machine. (It could differ on, say, .NET CF on an XBox 360.) There is no reason for Windows 7 x64 to be any different. Consequently, are you sure that BitConverter.IsLittleEndian is false? It should be true and therefore the behavior that you are seeing is correct.

    0 讨论(0)
  • 2020-12-03 12:00

    It's just:

    if (BitConverter.IsLittleEndian == true) Array.Reverse(var);
    
    0 讨论(0)
  • 2020-12-03 12:07

    Are you COMPLETELY sure that BitConverter.IsLittleEndian is returning false?

    If you inspect it through the debugger-watch before you have used any of it's methods you might get false even if it should return true.

    Read out the value through code to be completely certain. See also IsLittleEndian field reports false, but it must be Little-Endian?

    0 讨论(0)
  • 2020-12-03 12:09

    BinaryReader assumes Little Endian order: http://msdn.microsoft.com/en-us/library/system.io.binaryreader.readint64.aspx

    0 讨论(0)
  • 2020-12-03 12:15

    If you care about the endian-ness of your bytes, Jon Skeet wrote a class to allow you to choose the endian-order when you do the conversion.

    See C# little endian or big endian?

    0 讨论(0)
提交回复
热议问题