c# – double? = double?双?

我想ping StackOverflow社区,看看我是否失去了我的想法与这个简单的位的C#代码。

我在Windows 7上开发,在.NET 4.0,x64 Debug中构建这个。

我有以下代码:

static void Main()
{
    double? y = 1D;
    double? z = 2D;

    double? x;
    x = y + z;
}

如果我调试并在结束的大括号上放置一个断点,我期望在观察窗口和立即窗口中x = 3。 x = null。

如果我在x86中调试,事情似乎工作正常。是x64编译器出了问题,还是我的错误?

道格拉斯的答案是正确的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.5安装在.NET 4.0之上,我启动了一个虚拟机,并安装VS2010上。

在这里,我能够重现这个问题。在Main方法的关闭大括号上的断点处,在watch窗口和locals窗口中,我看到x为null。这是它开始有趣的地方。我瞄准了v2.0运行时,而发现它也是null。当然不能这样,因为我有相同版本的.NET 2.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()。

我不知道这个错误是否已经报告给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: 00000000
.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)

ef2)
****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

使用我们从Main中的clrstack获得的当前IP,我们看到在调用System.Nullable< double>的构造函数之后直接在指令上暂停执行。 (int 3是调试器停止执行所使用的中断)我已经用*括起了那一行,你也可以把它和IL中的L_0056匹配。

随后的x64程序集实际上将其分配给局部变量x。我们的指令指针尚未执行该代码,因此VS2010在x变量已由本地代码分配之前过早断开。

编辑:在x64中,int 3指令放在赋值代码之前,如上所示。在x86中,该指令位于赋值代码之后。这解释了为什么VS只突破x64的早期。很难说这是Visual Studio或JIT编译器的错误。我不确定哪个应用程序插入断点钩。

http://stackoverflow.com/questions/13648382/double-double-double

本站文章除注明转载外,均为本站原创或编译
转载请明显位置注明出处:c# – double? = double?双?