2010-01-05 10 views
13

Nel seguente codice, perché X e Y assumono valori diversi da quelli che penserei in modo intuitivo?Ordine di inversione dei byte in .NET

Se i byte da 0 a 7 sono scritti nel buffer, i byte lunghi risultanti non dovrebbero avere lo stesso ordine? È come se leggesse i valori lunghi in ordine inverso.

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

(non so cosa per etichettare questa domanda, oltre la semplice NET.)


BitConverter.IsLittleEndian 

è falso. Se il mio computer è big endian, perché succede?

  • Questo è un Windows 7 64-bit macchina
  • Core 2 Quad Q9400 2.66   GHz LGA 775 95W processore quad-core modello BX80580Q9400
  • SUPERMICRO MBD-C2SBX + -O LGA 775 Intel X48 ATX scheda madre Intel

I risultati di questo codice (in risposta a un commento di 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); 

Risultato:

False 
506097522914230528 
+1

tag Aggiunto per di cercare :) – spender

+0

Può darci le specifiche della macchina (processore, MB, O/S)? – GrayWizardx

+0

Questo è un Intel Core 2, Windows 7. Dovrei aggiungere anche che BitConverter.IsLittleEndian restituisce false. – Amy

risposta

20

BinaryReader.ReadInt64 è little endian di progettazione. Dalla documentazione:

BinaryReader legge questo tipo di dati in formato little-endian.

Infatti, è possibile ispezionare la fonte per BinaryReader.ReadInt64 utilizzando 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); 
} 

Questa che BinaryReader.ReadInt64 recita little endian indipendente dell'architettura sottostante.

Ora, BitConverter.ToInt64 si supponga di rispettare l'endianness della macchina sottostante. In Riflettore possiamo vedere

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

Così quello che vediamo qui è che se startIndex è congruente a zero modulo otto che un cast diretta è fatto da otto byte a partire dall'indirizzo numRef. Questo caso è gestito specialmente a causa di problemi di allineamento.La riga di codice

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

traduce direttamente al

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 

Così vediamo che in questo caso la chiave è l'istruzione ldind.i8. La CLI è agnostica sulla endianità della macchina sottostante. Permette al compilatore JIT di gestire questo problema. Su una macchina little-endian, ldind.i8 caricherà indirizzi più alti in bit più significativi e su una macchina big-endian ldind.i8 caricherà indirizzi più alti in byte meno significativi. Pertanto, in questo caso, endianness viene gestito correttamente.

Nell'altro caso, è possibile vedere che esiste un controllo esplicito della proprietà statica BitConverter.IsLittleEndian. Nel caso di little endian il buffer viene interpretato come little endian (in modo che la memoria { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07 } venga interpretata come il lungo 0x0706050403020100) e nel caso di big endian il buffer venga interpretato come big endian (in modo che la memoria venga interpretata come long 0x0001020304050607) . Quindi, per BitConverter tutto si riduce alla funzionalità del underyling. Prendo atto che sei su un chip Intel su Windows 7 x64. I chip Intel sono poco endian. Faccio notare che in Reflector, il costruttore statico per BitConverter è definito come il seguente:

static BitConverter() { 
    IsLittleEndian = true; 
} 

Questo è sulla mia macchina Windows Vista x64. (Potrebbe essere diverso, ad esempio, su .NET CF su XBox 360.) Windows 7 x64 non ha alcun motivo per essere diverso. Di conseguenza, sei sicuro che BitConverter.IsLittleEndian è false? Dovrebbe essere true e quindi il comportamento che stai vedendo è corretto.

+2

Questa è la singola risposta più utile che abbia mai trovato su StackOverflow. Grazie per aver eseguito il drill down nel comportamento delle istruzioni IL. Ero assolutamente sconcertato sul perché un certo pezzo di codice funzionasse prima che si verificasse la correzione endian esplicita. –

+0

Quindi c'è un modo per forzare la lettura della matrice in senso inverso (emettendo zeri iniziali, che diventano zeri finali dopo l'inversione). – Shimmy

5

Sei su una macchina little endian, dove interi sono memorizzati meno significativo Byte prima.

+2

Come persona non- .NET, sono sorpreso da questa domanda. Mi sarei aspettato che .NET potesse isolare il programmatore da endianness almeno quanto Java. –

+1

L'endianness della macchina è irrilevante, la CLI assume little endian. – mletterle

+2

Non è così. Neanche Java. L'ordine di inversione dei byte è * modo * troppo costoso. Non molto di un problema in questi giorni, Little Endian ha vinto. –

2

Se vi preoccupate per l'endian-ness dei vostri byte, Jon Skeet ha scritto una classe per consentire di scegliere l'endian ordine quando si esegue la conversione.

Vedi C# little endian or big endian?

3

Sei assolutamente sicuro che BitConverter.IsLittleEndian restituisce falso?

Se si ispeziona tramite il debugger-watch prima di aver utilizzato uno dei suoi metodi, è possibile che si ottenga false anche se deve restituire true.

Leggere il valore tramite codice per essere completamente certi. Vedere anche IsLittleEndian field reports false, but it must be Little-Endian?

2

E 'solo:

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

Eventuali spiegazioni su * perché * questo pezzo dovrebbe aiutare? – vyegorov

+0

Sono curioso anche di questo. Questo codice sembra risolvere il problema che sto avendo ma voglio essere sicuro di ciò che effettivamente fa. – hbulens

+0

Se si utilizza l'offset, ciò non funzionerà necessariamente. – Zapnologica

1

BitConverter utilizza l'endianness della macchina su cui sta girando. Per garantire un numero big-endian, utilizzare IPAddress.HostToNetworkOrder. Per esempio:

IPAddress.HostToNetworkOrder(BitConverter.ToInt64(buffer))