2014-11-07 12 views
8

byłem odkrywając IL code prostego programu:Jakie jest znaczenie -2 w niniejszej instrukcji IL?

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

Console.WriteLine(x); 

ja budować ten kod w wydania trybie i ten IL code jest generowany:

.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 

I zorientować się dość dużo wszystkie insructions wyjątkiem to:

IL_0010: ldc.i4.s -2 

Teraz ta insuracja ma znaczenie dpchnij int.MaxValue * 2L na stos, a następnie blt.s porówna go z i, jeśli i jest mniejsza niż wartość powrotu do IL_0008. Ale nie mogę zrozumieć, dlaczego to ładuje -2? Jeśli zmienię pętli jak poniżej:

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

Ładuje oczekiwaną wartość:

IL_0010: ldc.i8  0x17ffffffd 

Więc jaki jest sens -2 w tym kodzie?

+5

to jest optymalizacja, przy 3 bajty MSIL zamiast 9. Stała -2 jest samą optymalizacją, przyjmującą 1 bajt zamiast 4. Zauważ, że stała 0 w ogóle nie zajmuje miejsca, objętego dedykowanym kodem operacji. –

Odpowiedz

13

int.MaxValue * 2L to 64-bitowa liczba, która jednak nadal pasuje do 32-bitowych (4,294,967,294 lub 0xFFFFFFFE). Tak więc kompilator ładuje 0xFFFFFFFE (który jest równy -2 po interpretacji jako Int32), a następnie rozszerza go do niepodpisanej 64-bitowej wartości.

Powodem jest stosowany formularz podpisany jest to, że liczba, gdy interpretowane jako podpisany wartości -2, pasuje do jednego podpisanego bajt (-128 do 127), co oznacza, że ​​kompilator był w stanie emitować krótki formularz ldc.i4.s opcodu do załaduj 32-bitową wartość z jednego bajtu. Zajęło tylko 2 bajty załadowanie 32-bitowej liczby całkowitej ze znakiem i dodatkowego 1 bajta, aby przekonwertować ją na 64-bitową wartość - jest to znacznie lepsze niż użycie 64-bitowej instrukcji ładowania, po której następuje pełna 8-bajtowa liczba całkowita bez znaku.

+0

Prawdopodobnie bierze ten "skrót", aby kompilacja JIT działała szybciej. Nie tak duży nacisk na to, by IL był czytelny;) Uznano tę odpowiedź za poprawną, krótką i zwięzłą. – AlexanderBrevig

+0

@AlexanderBrevig: tak, ładowanie stałej 32-bitowej + kodowania conv.u8 zajmuje mniej miejsca niż ładowanie stałej 64-bitowej, prawdopodobnie takie jest uzasadnienie. Jak napisano powyżej @Hans, kod operacji 'ldc.i4.s' wymaga tylko jednego parametru sbyte (8-bitowa int) i rozszerza go do wartości 32-bitowej ze znakiem. – Groo

3

Wygląda na to, że kompilator używa bitowej matematyki na swoją korzyść. Tak się składa, że ​​wartość Two's Complement od -2 jest równa wartości liczb całkowitych (int.MaxValue * 2l)

w reprezentacji logiczną:

-           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