.NET反转字节顺序?字节、顺序、NET

2023-09-02 01:50:26 作者:分镜头.

在code以下,为什么X和Y采取不同的价值观比我会直觉地认为?如果这些字节0-7被写入缓冲器,不应该将所得长具有以相同的顺序字节?这就像它在相反的顺序读取长值。

  X 0x0706050403020100长
ÿ0x0706050403020100长
ž0x0001020304050607长

MemoryStream的毫秒=新的MemoryStream();
byte []的缓冲区=新的字节[] {0×00,0×01,0×02,0×03,0×04,0×05,0×06,0×07};
ms.Write(缓冲液,0,buffer.Length);
ms.Flush();
ms.Position = 0;

读者BinaryReader在新= BinaryReader在(MS);
长×= reader.ReadInt64();
长Y = BitConverter.ToInt64(缓冲液,0);
长Z = BitConverter.ToInt64(buffer.Reverse<字节>()的ToArray<字节>(),0);

byte []的xbytes = BitConverter.GetBytes(X);
byte []的ybytes = BitConverter.GetBytes(Y);
byte []的zbytes = BitConverter.GetBytes(Z);
 

(我不知道是什么标记这个问题,不仅仅是.NET)。

CnCerT.Net.SKiller3.31流量探测为何探测不到其他主机的字节数 求解

修改(赏金从这里开始)

  BitConverter.IsLittleEndian
 

是假的。如果我的电脑是大端的,为什么会出现这种情况?

编辑2

这是Windows 7 64位计算机 在英特尔酷睿2四核Q9400 2.66GHz的LGA 775 95W四核处理器型号BX80580Q9400 超微MBD-C2SBX + -O LGA 775的英特尔X48 ATX Intel主板

这code结果(响应杰森的评论):

 字节[]缓冲区=新的字节[] {0×00,0×00,0×01,0×02,0×03,0×04,0×05,0×06,0×07};
长Y = BitConverter.ToInt64(缓冲液,1);
Console.WriteLine(BitConverter.IsLittleEndian);
Console.WriteLine(Y);
 

结果:

 假
506097522914230528
 

解决方案

BinaryReader.ReadInt64是小端设计。从文档:

  

BinaryReader在读取小尾数格式此数据类型。

其实,我们可以检查源 BinaryReader.ReadInt64 使用反射。

 公共虚拟长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));
    返回(长)((NUM2<<为0x20)| NUM);
}
 

显示了 BinaryReader.ReadInt64 读独立于底层机器架构为小尾数。

现在,BitConverter.ToInt64是假设尊重你的底层机器的字节序。在反射器我们可以看到

 公共静态不安全长ToInt64(byte []的价值,诠释了startIndex){
    //参数检查省略
    固定(BYTE * numRef =及(值[在startIndex])){
        如果((在startIndex%8)== 0){
            回报*(((长*)numRef));
        }
        如果(IsLittleEndian){
            INT NUM =(numRef [0]<< 0×00)|
                      (numRef [1];&所述; 0x08的)|
                      (numRef [2]&其中;&所述;为0x10)|
                      (numRef [3]&其中;&所述;为0x18);
            INT NUM2 =(numRef [4]<< 0×00)|
                       (numRef [5]&其中;&所述; 0x08的)|
                       (numRef [6]&其中;&所述;为0x10)|
                       (numRef [7]&其中;&所述;为0x18);
            返回(((长)((ULONG)NUM))|(NUM2<<为0x20));
        }
        INT NUM3 =(numRef [0]<<为0x18)|
                   (numRef [1];&所述;为0x10)|
                   (numRef [2]&其中;&所述; 0x08的)|
                   (numRef [3]&其中;&所述; 0×00);
        INT num4 =(numRef [4]&其中;&所述;为0x18)|
                   (numRef [5]&其中;&所述;为0x10)|
                   (numRef [6]&其中;&所述; 0x08的)|
                   (numRef [7]&其中;&所述; 0×00);
        返回(((长)((ULONG)num4))|(NUM3<<为0x20));
}
 

所以,我们在这里看到的是,如果的startIndex 是一致的零模8的直接投从八个字节地址开始做 numRef 。由于定位问题,这种情况下,被特殊处理。的code行

 返回*(((长*)numRef));
 

直接转化为

  ldloc.0;推动本地0堆栈,这是numRef
    conv.i;堆栈的顶部弹出,转换为纯INT,推入堆栈
    ldind.i8;弹出地址关栈,从地址只要间接负荷
    RET;返回调用者,返回值是栈顶
 

所以我们看到,在这种情况下,关键是 ldind.i8 指令。 CLI是不可知对底层机器的字节序。它可以让JIT编译器处理这个问题。在一个小端机, ldind.i8 将加载高地址到更多的显著位和大端机器上 ldind.i8 将加载高地址到少显著字节。因此,在这种情况下,字节序是正确的处理。

在另一种情况下,你可以看到有静态属性的明确检查 BitConverter.IsLittleEndian 。在小端的情况下,缓冲区间preTED为小端(使内存 {0×00,0×01,0×02,0×03,0×04,0×05,0×06,0×07} 是PTED为长跨$ P $ 0x0706050403020100 ),如果是大端的缓冲区间preTED为大端(使内存 {0×00,0×01,0×02,0×03,0×04,0×05,0×06,0×07} 是PTED为跨$ P $长 0x0001020304050607 ) 。因此,对于 BitConverter 这一切都归结到underyling机器的字节序。我注意到,您使用的是Windows 7 x64上的英特尔芯片。英特尔芯片是小端。我注意到,在反射器,静态构造函数 BitConverter 被定义如下:

 静态BitConverter(){
    IsLittleEndian = TRUE;
}
 

这是在我的Windows Vista 64位的机器。 (它可以在不同的,比方说,.NET CF的Xbox 360),没有理由为Windows 7 64位系统有什么不同。因此,你确定 BitConverter.IsLittleEndian ?它应该是,因此,您所看到的行为是正确的。

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 long have bytes in the same order? It's like it's reading the long values in reverse order.

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

(I don't know what to tag this question, beyond just .net).

EDIT (Bounty started here)

BitConverter.IsLittleEndian 

is false. If my computer is big endian, why does this happen?

EDIT 2

This is a Windows 7 64-bit machine Intel Core2 Quad Q9400 2.66GHz LGA 775 95W Quad-Core Processor Model BX80580Q9400 SUPERMICRO MBD-C2SBX+-O LGA 775 Intel X48 ATX Intel Motherboard

The results of this code (in response to Jason's comment):

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

Result:

False    
506097522914230528

解决方案

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.