2011-07-26 9 views
3

ピアレビューの際にC#コードをデバッグしている間に、最初はスコープ違反のような奇妙な動作に気付きましたが、参照を再利用してメモリを節約する。コードは:MSILコード内の可変スコープ/再利用

for(int i = 0; i < 10; i++) 
{ 
    // Yadda yadda, something happens here 
} 

// At this point, i is out of scope and is not 
// accessible. This is verified by intellisense 
// and by attempting to look at the variable 
// during debug 
string whatever = ""; 

// At this point if I put a break on the following 
// for line, I can look at the variable I before 
// it is initialized and see that it already holds 
// the value of 10. If a different variable name 
// is used, I get a value of 0 (not initialized). 
for(int i = 0; i < 10; i++) 
{ 
    // Inside the loop, i has been re-initialized 
    // so it performs its function as expected 
} 

コンパイラは単に既存のリファレンスを再利用していますか?変数/参照をより密接に管理する必要のあるC/C++では、これは私が期待するような動作です。 C#では、ループの範囲内で変数が宣言されるたびに、メモリの新しい別のセクションを分割するという印象を受けましたが、明らかにそうではありません。これはメモリ節約機能ですか、C/C++の動作からのホールドオーバーですか?これはコンパイラが強制的に再初期化する必要があるため、この場合は無視されますか?

編集:
私は他のいくつかのチェックを行うことに気づいたいくつかのものは、この動作は、クラス内のメソッド間で発揮されないということです。複数の usingステートメントに現れますが、タイプと名前が同じ場合にのみ実行されます。

さらに調査すると、私は、これがMISLコードについてのものではなく、IDEがこれらの参照をそれ自身のメモリに保持していると考えています。私は、この動作が実際にコードレベルで存在することを示すものは何も見ていないので、今はIDEの単なる見解であるという考えに傾いています。

編集2:
@Vijayギルからの答えはIDEの癖を反証しているように見えます。

答えて

3

これは、コンパイラとコンパイルにどのような設定を使用しているかによって異なります。次のテキストダンプでは、リリースモードで2つのint変数が宣言されていることがわかります。-dubugモードでは1つだけです。

それはそれは全く私を超えない理由(私は家に行くとき、当分の間、私はより多くを調査します)

編集:この回答の終わり近くにもっと調査結果を参照してください

private static void f1() 
    { 
     for (int i = 0; i < 10; i++) 
     { 
      Console.WriteLine("Loop 1"); 
     } 

     Console.WriteLine("Interval"); 

     for (int i = 0; i < 10; i++) 
     { 
      Console.WriteLine("Loop 2"); 
     } 
    } 

リリースモード:(I & V_1ローカル変数に注意してください)

.method private hidebysig static void f1() cil managed 
{ 
    // Code size  57 (0x39) 
    .maxstack 2 
    .locals init ([0] int32 i, 
      [1] int32 V_1) 
    IL_0000: ldc.i4.0 
    IL_0001: stloc.0 
    IL_0002: br.s  IL_0012 
    IL_0004: ldstr  "Loop 1" 
    IL_0009: call  void [mscorlib]System.Console::WriteLine(string) 
    IL_000e: ldloc.0 
    IL_000f: ldc.i4.1 
    IL_0010: add 
    IL_0011: stloc.0 
    IL_0012: ldloc.0 
    IL_0013: ldc.i4.s 10 
    IL_0015: blt.s  IL_0004 
    IL_0017: ldstr  "Interval" 
    IL_001c: call  void [mscorlib]System.Console::WriteLine(string) 
    IL_0021: ldc.i4.0 
    IL_0022: stloc.1 
    IL_0023: br.s  IL_0033 
    IL_0025: ldstr  "Loop 2" 
    IL_002a: call  void [mscorlib]System.Console::WriteLine(string) 
    IL_002f: ldloc.1 
    IL_0030: ldc.i4.1 
    IL_0031: add 
    IL_0032: stloc.1 
    IL_0033: ldloc.1 
    IL_0034: ldc.i4.s 10 
    IL_0036: blt.s  IL_0025 
    IL_0038: ret 
} // end of method Program::f1 

デバッグモード:

.method private hidebysig static void f1() cil managed 
{ 
    // Code size  73 (0x49) 
    .maxstack 2 
    .locals init ([0] int32 i, 
      [1] bool CS$4$0000) 
    IL_0000: nop 
    IL_0001: ldc.i4.0 
    IL_0002: stloc.0 
    IL_0003: br.s  IL_0016 
    IL_0005: nop 
    IL_0006: ldstr  "Loop 1" 
    IL_000b: call  void [mscorlib]System.Console::WriteLine(string) 
    IL_0010: nop 
    IL_0011: nop 
    IL_0012: ldloc.0 
    IL_0013: ldc.i4.1 
    IL_0014: add 
    IL_0015: stloc.0 
    IL_0016: ldloc.0 
    IL_0017: ldc.i4.s 10 
    IL_0019: clt 
    IL_001b: stloc.1 
    IL_001c: ldloc.1 
    IL_001d: brtrue.s IL_0005 
    IL_001f: ldstr  "Interval" 
    IL_0024: call  void [mscorlib]System.Console::WriteLine(string) 
    IL_0029: nop 
    IL_002a: ldc.i4.0 
    IL_002b: stloc.0 
    IL_002c: br.s  IL_003f 
    IL_002e: nop 
    IL_002f: ldstr  "Loop 2" 
    IL_0034: call  void [mscorlib]System.Console::WriteLine(string) 
    IL_0039: nop 
    IL_003a: nop 
    IL_003b: ldloc.0 
    IL_003c: ldc.i4.1 
    IL_003d: add 
    IL_003e: stloc.0 
    IL_003f: ldloc.0 
    IL_0040: ldc.i4.s 10 
    IL_0042: clt 
    IL_0044: stloc.1 
    IL_0045: ldloc.1 
    IL_0046: brtrue.s IL_002e 
    IL_0048: ret 
} // end of method Program::f1 

アセンブリが生成されたコードは以下のとおりである(ローカル変数iに注意してください)。これは、リリースモードでのみコンパイルされたILに対するものです。今では機械語(ここでは逆アセンブル)で2つのローカル変数が作成されていることがわかります。私はそれに対する答えを見つけることができました。 MSの人だけが教えてくれます。しかし、この動作は、スタックの使用に関連して再帰的メソッドを記述するときに覚えておくことが非常に重要です。

00000000 push  ebp 
00000001 mov   ebp,esp 
00000003 sub   esp,0Ch 
00000006 mov   dword ptr [ebp-4],ecx 
00000009 cmp   dword ptr ds:[04471B50h],0 
00000010 je   00000017 
00000012 call  763A4647 

-- initialisation of local variables 
-- this is why we get all ints set to zero initially (will see similar behavioir for other types too) 
00000017 xor   edx,edx 
00000019 mov   dword ptr [ebp-8],edx 
0000001c xor   edx,edx 
0000001e mov   dword ptr [ebp-0Ch],edx 

00000021 xor   edx,edx -- zero out register edx which will be saved to memory where i (first one) is located 
00000023 mov   dword ptr [ebp-8],edx -- initialise variable i (first one) with 0 
00000026 nop 
00000027 jmp   00000037 -- jump to the loop condition 

00000029 mov   ecx,dword ptr ds:[01B32088h] 
0000002f call  76A84E7C -- calls method to print the message "Loop 1" 

00000034 inc   dword ptr [ebp-8] -- increment i (first one) by 1 
00000037 cmp   dword ptr [ebp-8],0Ah -- compare with 10 
0000003b jl   00000029 -- if still less, go to address 00000029 

0000003d mov   ecx,dword ptr ds:[01B3208Ch] 
00000043 call  76A84E7C -- prints the message "Half way there" 

00000048 xor   edx,edx -- zero out register edx which will be saved to memory where i (second one) is located 
0000004a mov   dword ptr [ebp-0Ch],edx -- initialise i (second one) with 0 
0000004d nop 
0000004e jmp   0000005E -- jump to the loop condition 

00000050 mov   ecx,dword ptr ds:[01B32090h] 
00000056 call  76A84E7C -- calls method to print the message "Loop 1" 

0000005b inc   dword ptr [ebp-0Ch] -- increment i (second one) by 1 
0000005e cmp   dword ptr [ebp-0Ch],0Ah -- compare with 10 
00000062 jl   00000050 -- if still less, go to address 00000050 


00000064 nop 
00000065 mov   esp,ebp 
00000067 pop   ebp 
00000068 ret 
+0

スイッチ「最適化コード」によって異なります。リリース構成で最適化されたコードスイッチが無効になっている場合、デバッグのように反応します。 – fixagon

+0

私も見ました:)私の主な理由は、「より多くの調査」は、なぜ2つの変数を使用する方が最適なのかを知ることです。 –

+0

これは本当に面白いです。私はこれをIDEクォークに向かって傾いていましたが、ここであなたの記事を見てみると、これはまったくないかもしれません。 –

3
それはコンパイラが同じ変数再利用することを、そのようにする必要があり

: を(それはすでにあなたの例と最も可能性の高いだったが、ただ、本当に同じアドレスが使用されていることを示すために...)

証明:(両方の変数が同じメモリアドレスを共有する)

public unsafe void test() 
{ 
    for (int i = 0; i < 10; i++) 
    { 
     // Yadda yadda, something happens here 
     int* ptr = &i; 
     IntPtr addr = (IntPtr)ptr; 
     if (i == 9) 
     { 
      Console.WriteLine(addr.ToString("x")); 
      MessageBox.Show(addr.ToString("x")); 
     } 
    } 

    for (int i = 0; i < 10; i++) 
    { 
     int* ptr = &i; 
     IntPtr addr = (IntPtr)ptr; 
     if (i == 9) 
     { 
      Console.WriteLine(addr.ToString("x")); 
      MessageBox.Show(addr.ToString("x")); 
     } 
    } 
} 

逆コンパイルバージョンを確認するためにinterrestingされるだろう。

+0

間にガベージコレクションが発生する可能性がありますか?これは何か違いがありますか? – Matt

+2

変数 "i"は値型でスタック上に作成されるため、GCは決してそれに触れず、メソッドが終了するとそのメモリは再利用されます。 –

関連する問題