ダグラスの答えは、JIT最適化デッドコードについて正しいです(x86コンパイラとx64コンパイラの両方がこれを行います)。ただし、JITコンパイラがデッドコードを最適化している場合はx
、[ローカル]ウィンドウにも表示されないため、すぐにわかります。さらに、ウォッチとイミディエイトウィンドウにアクセスしようとすると、代わりに「名前'x'は現在のコンテキストに存在しません」というエラーが表示されます。それはあなたが起こっているとあなたが説明したことではありません。
表示されているのは、実際にはVisualStudio2010のバグです。
まず、メインマシンであるWin7x64とVS2012でこの問題を再現しようとしました。.NET 4.0ターゲットのx
場合、閉じ中括弧で壊れたときは3.0Dに等しくなります。.NET 3.5ターゲットも試してみることにしましたx
。これにより、nullではなく3.0Dに設定されました。
.NET4.0の上に.NET4.5をインストールしているため、この問題を完全に再現することはできないため、仮想マシンを起動してVS2010をインストールしました。
ここで、問題を再現することができました。メソッドの閉じ中括弧にブレークポイントがありMain
、ウォッチウィンドウとローカルウィンドウの両方で、それがであることがわかりましx
たnull
。これはそれが面白くなり始めるところです。代わりにv2.0ランタイムをターゲットにしたところ、そこでもnullであることがわかりました。他のコンピューターに同じバージョンの.NET2.0ランタイムがx
あり、値が.で正常に表示されているため、これは当てはまりません3.0D
。
では、何が起こっているのでしょうか。windbgを掘り下げた後、問題が見つかりました。
VS2010は、実際に割り当てられる前のxの値を表示しています。
命令ポインタが行を超えているので、それがどのように見えるかではないことを私は知っていますx = y + z
。メソッドに数行のコードを追加することで、これを自分でテストできます。
double? y = 1D;
double? z = 2D;
double? x;
x = y + z;
Console.WriteLine(); // Don't reference x here, still leave it as dead code
最後の中括弧にブレークポイントがあると、ローカルとウォッチウィンドウはx
に等しいと表示され3.0D
ます。ただし、コードをステップスルーすると、をステップスルーするまで、 VS2010がx
割り当て済みとして表示されないことがわかります。Console.WriteLine()
このバグがMicrosoftConnectに報告されたことがあるかどうかはわかりませんが、このコードを例として、報告することをお勧めします。ただし、VS2012で明らかに修正されているため、これを修正するための更新があるかどうかはわかりません。
JITとVS2010で実際に起こっていることは次のとおりです
元のコードを使用すると、VSが何を行っているのか、なぜそれが間違っているのかを確認できます。また、変数が最適化されていないこともわかりx
ます(最適化を有効にしてコンパイルするアセンブリをマークしていない限り)。
まず、ILのローカル変数の定義を見てみましょう。
.locals init (
[0] valuetype [mscorlib]System.Nullable`1<float64> y,
[1] valuetype [mscorlib]System.Nullable`1<float64> z,
[2] valuetype [mscorlib]System.Nullable`1<float64> x,
[3] valuetype [mscorlib]System.Nullable`1<float64> CS$0$0000,
[4] valuetype [mscorlib]System.Nullable`1<float64> CS$0$0001,
[5] valuetype [mscorlib]System.Nullable`1<float64> CS$0$0002)
これは、デバッグモードでの通常の出力です。Visual Studioは、割り当て中に使用する重複するローカル変数を定義し、さらにILコマンドを追加して、CS*変数からそれぞれのユーザー定義のローカル変数にコピーします。これが起こっていることを示す対応するILコードは次のとおりです。
// For the line x = y + z
L_0045: ldloca.s CS$0$0000 // earlier, y was stloc.3 (CS$0$0000)
L_0047: call instance !0 [mscorlib]System.Nullable`1<float64>::GetValueOrDefault()
L_004c: conv.r8 // Convert to a double
L_004d: ldloca.s CS$0$0001 // earlier, z was stloc.s CS$0$0001
L_004f: call instance !0 [mscorlib]System.Nullable`1<float64>::GetValueOrDefault()
L_0054: conv.r8 // Convert to a double
L_0055: add // Add them together
L_0056: newobj instance void [mscorlib]System.Nullable`1<float64>::.ctor(!0) // Create a new nulable
L_005b: nop // NOPs are placed in for debugging purposes
L_005c: stloc.2 // Save the newly created nullable into `x`
L_005d: ret
WinDbgを使用してさらに詳細なデバッグを行いましょう。
VS2010でアプリケーションをデバッグし、メソッドの最後にブレークポイントを残すと、非侵襲的モードでWinDbgを簡単にアタッチできます。
Main
これが、呼び出しスタック内のメソッドのフレームです。IP(命令ポインタ)が気になります。
0:009>!clrstack
OSスレッドID:0x135c(9)
子SPIPコールサイト
000000001c48dc00 000007ff0017338d ConsoleApplication1.Program.Main(System.String [])
[等々...]
メソッドのネイティブマシンコードを表示するMain
と、VSが実行を中断したときに実行された命令を確認できます。
000007ff`00173388e813fe25f2呼び出しmscorlib_ni+0xd431a0
(000007fe`f23d31a0)(System.Nullable`1 [[System.Double、mscorlib]] .. ctor(Double)、mdToken:0000000006001ef2)
**** 000007ff`0017338d cc int 3 ****
000007ff`0017338e 8d8c2490000000 lea ecx、[rsp + 90h]
000007ff`00173395 488b01 mov rax、qword ptr [rcx]
000007ff`00173398 4889842480000000 mov qword ptr [rsp + 80h]、rax
000007ff`001733a0 488b4108 mov rax、qword ptr [rcx + 8]
000007ff`001733a4 4889842488000000 mov qword ptr [rsp + 88h]、rax
000007ff`001733ac 488d8c2480000000 lea rcx、[rsp + 80h]
000007ff`001733b4 488b01 mov rax、qword ptr [rcx]
000007ff`001733b7 4889442440 mov qword ptr [rsp + 40h]、rax
000007ff`001733bc 488b4108 mov rax、qword ptr [rcx + 8]
000007ff`001733c0 4889442448 mov qword ptr [rsp + 48h]、rax
000007ff`001733c5 eb00 jmp 000007ff`001733c7
000007ff`001733c7 0f28b424c0000000 movaps xmm6、xmmword ptr [rsp + 0C0h]
000007ff`001733cf 4881c4d8000000 add rsp、0D8h
000007ff`001733d6 c3 ret
!clrstack
で取得した現在のIPを使用すると、のコンストラクターを呼び出した直後Main
に、命令の実行が中断されたことがわかります。(デバッガーが実行を停止するために使用する割り込みです)その行を*で囲みました。また、行をILで一致させることもできます。System.Nullable<double>
int 3
L_0056
次のx64アセンブリは、実際にそれをローカル変数に割り当てますx
。命令ポインターはまだそのコードを実行していないため、VS2010は、x
変数がネイティブコードによって割り当てられる前に、時期尚早に壊れています。
編集:x64では、上記のように、int 3
命令は割り当てコードの前に配置されます。x86では、その命令は割り当てコードの後に配置されます。これが、VSがx64でのみ早期に機能しなくなっている理由を説明しています。これがVisualStudioまたはJITコンパイラのせいであるかどうかを判断するのは難しいです。どのアプリケーションがブレークポイントフックを挿入するかわかりません。