2010-01-05 8 views
13

Warum nehmen X und Y im folgenden Code andere Werte an als das, was ich intuitiv denken würde?Umkehr der Byte-Reihenfolge in .NET

Wenn die Bytes 0-7 in den Puffer geschrieben werden, sollte die resultierende Länge nicht Bytes in der gleichen Reihenfolge haben? Es ist so, als würde man die langen Werte in umgekehrter Reihenfolge lesen.

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

(Ich weiß nicht, was diese Frage zu markieren, über die reine .NET.)


BitConverter.IsLittleEndian 

ist falsch. Wenn mein Computer Big Endian ist, warum passiert das?

  • Dies ist ein Windows 7 64-Bit-Maschine
  • Intel Core2 Quad Q9400 2.66   GHz LGA 775 95W Quad-Core Prozessor-Modell BX80580Q9400
  • SUPERMICRO MBD-C2SBX + -O LGA 775 Intel X48 ATX Intel Motherboard

Die Ergebnisse dieses Codes (als Antwort auf Jasons Kommentar):

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

Ergebnis:

False 
506097522914230528 
+1

Added-Tag für Sie :) – spender

+0

Können Sie die Spezifikationen der Maschine (Prozessor, mb, O/S) geben suchen? – GrayWizardx

+0

Dies ist ein Intel Core 2, Windows 7. Ich sollte auch hinzufügen, dass BitConverter.IsLittleEndian false zurückgibt. – Amy

Antwort

20

BinaryReader.ReadInt64 Little-Endian ist konstruktionsbedingt. Aus der Dokumentation:

BinaryReader liest diesen Datentyp im Little-Endian-Format.

In der Tat können wir die Quelle für BinaryReader.ReadInt64 mit Reflektor überprüfen.

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

Zeigt, dass BinaryReader.ReadInt64 so wenig Endian unabhängig von der zugrunde liegenden Maschinenarchitektur liest.

Jetzt sollte BitConverter.ToInt64 die Endianz Ihrer zugrunde liegenden Maschine respektieren. In Reflector können wir

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 sehen, was wir hier sehen, ist, dass, wenn startIndex kongruent zu Null modulo acht, die eine direkte Besetzung von acht Bytes ab Adresse erfolgt numRef. Dieser Fall wird speziell aufgrund von Ausrichtungsproblemen behandelt.Die Codezeile

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

sich direkt in

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 sehen wir in diesem Fall, dass der Schlüssel ist die ldind.i8 Anweisung. Die CLI ist agnostisch bezüglich der Endianität der zugrunde liegenden Maschine. Dadurch kann der JIT-Compiler dieses Problem behandeln. Auf einer Little-Endian-Maschine lädt ldind.i8 höhere Adressen in höherwertige Bits und auf einer Big-Endian-Maschine lädt ldind.i8 höhere Adressen in weniger signifikante Bytes. Daher wird in diesem Fall die Endiannität richtig gehandhabt. Im anderen Fall können Sie sehen, dass die statische Eigenschaft BitConverter.IsLittleEndian explizit überprüft wird. Im Fall von Little Endian wird der Puffer als Little Endian interpretiert (so dass der Speicher { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07 } als Long 0x0706050403020100 interpretiert wird) und im Fall von Big Endian wird der Puffer als Big Endian interpretiert (so dass der Speicher { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07 } als Long 0x0001020304050607 interpretiert wird) . Also, für BitConverter kommt es auf die Endianness der Underinging Maschine an. Ich stelle fest, dass Sie auf einem Intel-Chip unter Windows 7 x64 sind. Intel-Chips sind Little Endian. Ich nehme zur Kenntnis, dass in Reflektor, der statische Konstruktor für BitConverter wie folgt definiert ist:

static BitConverter() { 
    IsLittleEndian = true; 
} 

Dies ist auf meinem Windows Vista x64-Maschine. (Es könnte sich beispielsweise bei .NET CF auf einer XBox 360 unterscheiden.) Es gibt keinen Grund dafür, dass Windows 7 x64 anders ist. Sind Sie also sicher, dass BitConverter.IsLittleEndianfalse ist? Es sollte true sein und daher ist das Verhalten, das Sie sehen, korrekt.

+2

Dies ist die nützlichste Antwort, die ich je auf Stackoverflow gefunden habe. Danke, dass Sie sich bis zum Verhalten der IL-Anweisungen durchgearbeitet haben. Ich war absolut verblüfft darüber, warum ein bestimmter Code funktionierte, bevor eine explizite Endian-Korrektur stattfand. –

+0

So gibt es eine Möglichkeit, das Array zu zwingen, umgekehrt gelesen zu werden (führende Nullen, die nach der Umkehrende Nullen werden). – Shimmy

5

Sie auf einem little endian Maschine sind, wo ganze Zahlen zuerst am wenigsten signifikanten Byte gespeichert werden.

+2

Als Nicht-.NET-Person bin ich über diese Frage überrascht. Ich hätte erwartet, dass .NETs den Programmierer von Endianess mindestens genauso stark isoliert wie Java. –

+1

Die Endianess der Maschine ist irrelevant, die CLI nimmt Little Endian an. – mletterle

+2

Es tut es nicht. Ebensowenig Java. Umkehrreihenfolge ist * weg * zu teuer. Nicht viel von einem Problem in diesen Tagen hat Little Endian gewonnen. –

2

Wenn Sie über die Bytereihenfolge Ihrer Bytes kümmern, Jon Skeet eine Klasse geschrieben, damit Sie die Endian-Ordnung wählen, wenn Sie die Konvertierung zu tun.

Siehe C# little endian or big endian?

3

Sind Sie absolut sicher, dass BitConverter.IsLittleEndian false zurückgibt?

Wenn Sie es über die Debugger-Uhr überprüfen, bevor Sie eine seiner Methoden verwendet haben, erhalten Sie möglicherweise auch dann false, wenn es wahr zurückgeben sollte.

Lesen Sie den Wert durch Code, um ganz sicher zu sein. Siehe auch IsLittleEndian field reports false, but it must be Little-Endian?

2

Es ist einfach:

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

Irgendwelche Erklärungen zu * warum * dieses Stück sollte helfen? – vyegorov

+0

Ich bin neugierig auf diese auch. Dieser Code scheint das Problem zu lösen, das ich habe, aber ich möchte sicher sein, was es tatsächlich tut. – hbulens

+0

Wenn Sie Offset verwenden, funktioniert dies nicht unbedingt. – Zapnologica

1

BitConverter die endianness der Maschine nutzt es läuft weiter. Verwenden Sie IPAddress.HostToNetworkOrder, um eine Big-Endian-Nummer zu gewährleisten. Zum Beispiel:

IPAddress.HostToNetworkOrder(BitConverter.ToInt64(buffer))