2014-11-07 10 views
8

ich die IL code eines einfachen Programms zu entdecken:Was bedeutet -2 in diesem IL-Befehl?

long x = 0; 
for(long i = 0;i< int.MaxValue * 2L; i++) 
{ 
    x = i; 
} 

Console.WriteLine(x); 

Ich baue diesen Code in Veröffentlichung Modus und diese IL code generiert:

.method private hidebysig static void Main(string[] args) cil managed 
{ 
    .entrypoint 
    // Code size  28 (0x1c) 
    .maxstack 2 
    .locals init ([0] int64 x, 
      [1] int64 i) 
    IL_0000: ldc.i4.0 
    IL_0001: conv.i8 
    IL_0002: stloc.0 
    IL_0003: ldc.i4.0 
    IL_0004: conv.i8 
    IL_0005: stloc.1 
    IL_0006: br.s  IL_000f 
    IL_0008: ldloc.1 
    IL_0009: stloc.0 
    IL_000a: ldloc.1 
    IL_000b: ldc.i4.1 
    IL_000c: conv.i8 
    IL_000d: add 
    IL_000e: stloc.1 
    IL_000f: ldloc.1 
    IL_0010: ldc.i4.s -2 
    IL_0012: conv.u8 
    IL_0013: blt.s  IL_0008 
    IL_0015: ldloc.0 
    IL_0016: call  void [mscorlib]System.Console::WriteLine(int64) 
    IL_001b: ret 
} // end of method Program::Main 

ich so ziemlich die insructions alle herauszufinden, außer dies:

IL_0010: ldc.i4.s -2 

Jetzt diese Anleitung d int.MaxValue * 2L auf den Stapel schieben und dann wird blt.s es mit i vergleichen, wenn i weniger ist als der Wert auf den IL_0008 .Aber zurück, was ich kann nicht herausfinden, das ist, warum es -2 lädt? Wenn ich die Schleife wie unten ändern:

for(long i = 0;i < int.MaxValue * 3L; i++) 
{ 
    x = i; 
} 

Es lädt den erwarteten Wert:

IL_0010: ldc.i8  0x17ffffffd 

Also, was ist die Bedeutung von -2 in diesem Code?

+5

Es ist eine Optimierung unter 3 Byte MSIL statt 9. Die Konstante -2 ist eine Optimierung selbst und nimmt 1 Byte statt 4 auf. Beachten Sie, dass die 0-Konstante überhaupt keinen Speicherplatz benötigt, und zwar durch einen dedizierten Opcode. –

Antwort

13

int.MaxValue * 2L ist eine 64-Bit-Nummer, die jedoch immer noch in 32-Bit passt (4,294,967,294 oder 0xFFFFFFFE). Also lädt der Compiler 0xFFFFFFFE (was gleich -2 ist, wenn er als Int32 interpretiert wird) und erweitert ihn dann auf einen vorzeichenlosen 64-Bit-Wert.

Der Grund ist es die unterzeichnete Form verwendet wird, dass die Zahl, wenn sie als ein Wert mit Vorzeichen interpretiert -2, paßt in ein einziges Byte mit Vorzeichen (-128 zu 127), was bedeutet, dass der Compiler in der Lage war, die Kurzform ldc.i4.s Opcode zu emittieren, um Laden Sie einen 32-Bit-Wert von einem einzelnen Byte. Es dauerte nur 2 Bytes, um die 32-Bit-Ganzzahl mit Vorzeichen zu laden, und zusätzlich 1 Byte, um es in einen 64-Bit-Wert zu konvertieren - das ist viel besser als eine 64-Bit-Ladeanweisung, gefolgt von einer vollen 8-Byte-Ganzzahl ohne Vorzeichen.

+0

Es dauert wahrscheinlich diese "Verknüpfung", um JIT kompilieren schneller zu machen. Nicht so sehr darauf, IL lesbar zu machen;) Upvoted diese Antwort, wie sie korrekt ist, kurz und prägnant. – AlexanderBrevig

+0

@AlexanderBrevig: Ja, das Laden eines 32-Bit-Konstanten + Conv.u8-Opcodes benötigt weniger Platz als das Laden einer 64-Bit-Konstanten, wahrscheinlich ist das der Grund. Wie @Hans oben geschrieben hat, benötigt der 'ldc.i4.s' Opcode nur einen einzelnen sbyte (signed 8-bit int) -Parameter und erweitert ihn auf einen vorzeichenbehafteten 32-Bit-Wert. – Groo

3

Es sieht so aus, als ob der Compiler bitweise Mathematik zu seinem Vorteil verwendet. Es passiert einfach, so dass der Two's Complement Wert von -2 bis den ganzen Zahlen ohne Vorzeichen Wert von (int.MaxValue * 2L) gleich

In der bitweisen Darstellung:

-           1111 1111 1111 1111 1111 1111 1111 1110 (int) 
-           1111 1111 1111 1111 1111 1111 1111 1110 (uint) 
- 0000 0000 0000 0000 0000 0000 0000 0000 1111 1111 1111 1111 1111 1111 1111 1110 (long