2017-07-13 1 views
4

this old questionに投稿されたコードの逆アセンブリを調べようとしましたが、何か奇妙なものが見つかりました。ここでキャプチャデリゲートでループを逆アセンブルするためのC#で役に立たない変数?

は、ソースコードは、明確化のために、です:

class ThreadTest 
{ 
    static void Main(string[] args) 
    { 
     for (int i = 0; i < 10; i++) 
      new Thread(() => Console.WriteLine(i)).Start(); 
    } 
} 

(もちろん、このプログラムの動作が予想外で、それはここでは問題ではありません。)

ここで私は見て見たものです解体時:

internal class ThreadTest 
{ 
    private static void Main(string[] args) 
    { 
     int i; 
     int j; 
     for (i = 0; i < 10; i = j + 1) 
     { 
      new Thread(delegate 
      { 
       Console.WriteLine(i); 
      }).Start(); 
      j = i; 
     } 
    } 
} 

jは何ですか?ここにバイトコードがあります:

.method private hidebysig static 
    void Main (
     string[] args 
    ) cil managed 
{ 
    // Method begins at RVA 0x2050 
    // Code size 64 (0x40) 
    .maxstack 2 
    .entrypoint 
    .locals init (
     [0] class ConsoleApplication2.ThreadTest/'<>c__DisplayClass0_0' 'CS$<>8__locals0', 
     [1] int32 
    ) 

    IL_0000: newobj instance void ConsoleApplication2.ThreadTest/'<>c__DisplayClass0_0'::.ctor() 
    IL_0005: stloc.0 
    IL_0006: ldloc.0 
    IL_0007: ldc.i4.0 
    IL_0008: stfld int32 ConsoleApplication2.ThreadTest/'<>c__DisplayClass0_0'::i 
    IL_000d: br.s IL_0035 
    // loop start (head: IL_0035) 
     IL_000f: ldloc.0 
     IL_0010: ldftn instance void ConsoleApplication2.ThreadTest/'<>c__DisplayClass0_0'::'<Main>b__0'() 
     IL_0016: newobj instance void [mscorlib]System.Threading.ThreadStart::.ctor(object, native int) 
     IL_001b: newobj instance void [mscorlib]System.Threading.Thread::.ctor(class [mscorlib]System.Threading.ThreadStart) 
     IL_0020: call instance void [mscorlib]System.Threading.Thread::Start() 
     IL_0025: ldloc.0 
     IL_0026: ldfld int32 ConsoleApplication2.ThreadTest/'<>c__DisplayClass0_0'::i 
     IL_002b: ldc.i4.1 
     IL_002c: add 
     IL_002d: stloc.1 
     IL_002e: ldloc.0 
     IL_002f: ldloc.1 
     IL_0030: stfld int32 ConsoleApplication2.ThreadTest/'<>c__DisplayClass0_0'::i 

     IL_0035: ldloc.0 
     IL_0036: ldfld int32 ConsoleApplication2.ThreadTest/'<>c__DisplayClass0_0'::i 
     IL_003b: ldc.i4.s 10 
     IL_003d: blt.s IL_000f 
    // end loop 

    IL_003f: ret 
} // end of method ThreadTest::Main 

しかし、ここは変です。私はi = i + 1i++を交換し、このような元のコード、変更する場合:

class ThreadTest 
{ 
    static void Main(string[] args) 
    { 
     for (int i = 0; i < 10; i = i + 1) 
      new Thread(() => Console.WriteLine(i)).Start(); 
    } 
} 

を私はこれを取得:

私は期待まさにある
internal class ThreadTest 
{ 
    private static void Main(string[] args) 
    { 
     int i; 
     for (i = 0; i < 10; i++) 
     { 
      new Thread(delegate 
      { 
       Console.WriteLine(i); 
      }).Start(); 
     } 
    } 
} 

はここでバイトコードです:

.method private hidebysig static 
    void Main (
     string[] args 
    ) cil managed 
{ 
    // Method begins at RVA 0x2050 
    // Code size 62 (0x3e) 
    .maxstack 3 
    .entrypoint 
    .locals init (
     [0] class ConsoleApplication2.ThreadTest/'<>c__DisplayClass0_0' 'CS$<>8__locals0' 
    ) 

    IL_0000: newobj instance void ConsoleApplication2.ThreadTest/'<>c__DisplayClass0_0'::.ctor() 
    IL_0005: stloc.0 
    IL_0006: ldloc.0 
    IL_0007: ldc.i4.0 
    IL_0008: stfld int32 ConsoleApplication2.ThreadTest/'<>c__DisplayClass0_0'::i 
    IL_000d: br.s IL_0033 
    // loop start (head: IL_0033) 
     IL_000f: ldloc.0 
     IL_0010: ldftn instance void ConsoleApplication2.ThreadTest/'<>c__DisplayClass0_0'::'<Main>b__0'() 
     IL_0016: newobj instance void [mscorlib]System.Threading.ThreadStart::.ctor(object, native int) 
     IL_001b: newobj instance void [mscorlib]System.Threading.Thread::.ctor(class [mscorlib]System.Threading.ThreadStart) 
     IL_0020: call instance void [mscorlib]System.Threading.Thread::Start() 
     IL_0025: ldloc.0 
     IL_0026: ldloc.0 
     IL_0027: ldfld int32 ConsoleApplication2.ThreadTest/'<>c__DisplayClass0_0'::i 
     IL_002c: ldc.i4.1 
     IL_002d: add 
     IL_002e: stfld int32 ConsoleApplication2.ThreadTest/'<>c__DisplayClass0_0'::i 

     IL_0033: ldloc.0 
     IL_0034: ldfld int32 ConsoleApplication2.ThreadTest/'<>c__DisplayClass0_0'::i 
     IL_0039: ldc.i4.s 10 
     IL_003b: blt.s IL_000f 
    // end loop 

    IL_003d: ret 
} // end of method ThreadTest::Main 

なぜコンパイラは、最初のシナリオでjを追加しましたか?

注:リリースモードでコンパイルするVS 2015 Update 3、.NET Framework 4.5.2を使用しています。あなたもこれを行うことができますので、

+0

'for(i = 0; i <10; ++ i)' –

+0

についても同じことを行います。どのようにコードを '逆アセンブルしましたか? – mjwills

+0

@mjwills:ILSpyを使用しました。 – themiurge

答えて

3

意味的には、i++と書くと、コンパイラは元の値iを保存する必要があるため、結果の式の値として使用できます。

コンパイラは、必要に応じて、古い値iが使用されるまで、新しい値を保持できる新しい変数を導入して、これを実装します。したがって、更新されたjの値がiにコピーされるまで、古い値iは読み取ることができます。もちろん、この場合は、add命令の結果をjにコピーした直後に発生します。コードでは実際にはその値は必要ありません。しかし、しばらくの間、iの値は古いものにとどまり、必要であれば使用されました。私はその値を使用したことがない、

をしかし:

あなたは主張するかもしれません。なぜコンパイラはそれを保つのですか?最初にjに保存するのではなく、addの結果をiに直接書き込むのはなぜですか?

C#コンパイラは最適化を担当していません。その主な仕事は、C#コードをILに変換することです。実際には、私はこの仕事の一部はではなく、は、最適化を担当しているであるJITコンパイラで作業を簡単にするために、実装の一般的なパターンに従うことが非常に難しいです。

このような縮退シナリオを最適化するロジックを含まないことで、C#コンパイラが正しいILを生成していることを確認しやすくなり、予測可能で簡単に最適化することができます。

+0

"C#コンパイラは最適化を担当していません"。C + +のバックグラウンドから来て、それはまさに私が欠けていた部分です。ありがとう。 – themiurge

0

i++は正確にi = i + 1ではありません。

このコードを試してみてください。

int i = 1; 
int x = 5 + i++; 
Console.WriteLine("i:" + i + " x: " + x); 
i = 1; 
int y = 5 + ++i; 
Console.WriteLine("i:" + i + " y: " + y); 

出力:

i:2 x: 6 
i:2 y: 7 

これは/プレフィックスと後置インクリメントに関係しています減少する(How do Prefix (++x) and Postfix (x++) operations work?参照)。

+0

私はプレフィックス/後置インクリメントがどのように動作するかを知っています。 – themiurge

+3

そうです。 C#コンパイラは、JITコンパイラがそれをクリーンアップすると仮定して、 "ダム" ILコードを生成しているように見えます。結果は破棄されるので、 'i ++'と '++ i'の両方はこの文脈で同じことをしますが、コンパイラがこれを検出してJITterに任せてしまうほど巧妙ではないかもしれません。 –

+0

@MichaelGeary:それは私が必要としていた説明です。ありがとう。 – themiurge

関連する問題