道格拉斯的回答是正確的關於JIT優化死代碼(都 x86和x64編譯器將這樣做)。但是,如果JIT編譯器優化了死代碼,它將立即顯而易見,因爲x
甚至不會出現在本地窗口中。此外,當試圖訪問它時,watch和immediate窗口會給你一個錯誤:「名稱'x'在當前上下文中不存在」。這不是你所描述的情況。
你們看到的實際上是在Visual Studio 2010
首先一個錯誤,我試圖模仿我的主機對這個問題:Win7x64和VS2012。對於.NET 4.0目標,x
在關閉大括號中斷時等於3.0D。我決定嘗試.NET 3.5的目標,並且,x
也被設置爲3.0D,而不是null。
由於我不能完全再現這個問題,因爲我已經在.NET 4.0之上安裝了.NET 4.5,所以我創建了一個虛擬機並在其上安裝了VS2010。
在這裏,我能夠重現這個問題。在Main
方法的關閉花括號上的斷點處,在觀察窗口和當地人窗口中,我看到x
是null
。這是它開始變得有趣的地方。我將目標定位在v2.0運行時,並發現它也是空的。當然,情況並非如此,因爲我的另一臺成功顯示x
的計算機具有相同版本的.NET 2.0運行時,其值爲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()
。
我不知道這個bug是否曾報告給Microsoft Connect,但您可能想要這樣做,以此代碼爲例。然而,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 Thread Id: 0x135c (9)
Child SP IP Call Site
000000001c48dc00 000007ff0017338d ConsoleApplication1.Program.Main(System.String[])
[And so on...]
如果我們查看了Main
方法的本機代碼,我們可以看到什麼指令已經在那個VS打破執行的時間已經運行:
000007ff`00173388 e813fe25f2 call 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
使用當前IP,我們從得到!clrstack
in Main
,我們看到執行被暫停在指令之後,直接在之後調用System.Nullable<double>
的構造函數。(int 3
是調試器用於停止執行的中斷)我用*標記了該行,並且您還可以在IL中匹配L_0056
。
下面的x64程序集實際上將它分配給本地變量x
。我們的指令指針還沒有執行的代碼還,所以VS2010過早打破之前x
變量已經由本機代碼分配。
編輯:在x64中,int 3
指令位於賦值代碼之前,如上所示。在x86中,該指令位於分配代碼之後。這就解釋了爲什麼VS只在64位時纔會破產。很難說如果這是Visual Studio或JIT編譯器的錯誤。我不確定哪個應用程序插入斷點鉤子。
ideone運行單聲道,它按預期工作。 http://ideone.com/DbuxwD –
這與我的興趣相關。現在我們只需要等待Skeet先生。 – MikeTheLiar
它可能是調試器?嘗試在最後放置一個Console.WriteLine()並查看它打印出來的內容。 – siride