c# - 双? = 双? + 双?

标签 c# .net

我想 ping StackOverflow 社区,看看我是否因为这个简单的 C# 代码而失去理智。

我正在 Windows 7 上开发,在 .NET 4.0、x64 调试中构建它。

我有以下代码:

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

    double? x;
    x = y + z;
}

如果我调试并在结尾的花括号上放置一个断点,我希望在监视窗口和立即窗口中 x = 3。 x = null 代替。

如果我在 x86 中调试,一切似乎都正常。 x64 编译器有问题还是我有问题?

最佳答案

Douglas 对 JIT 优化死代码的回答是正确的(x86 和 x64 编译器都会这样做)。但是,如果 JIT 编译器正在优化死代码,这将立即显而易见,因为 x甚至不会出现在本地窗口中。此外,监视和立即窗口在尝试访问它时会出现错误:“当前上下文中不存在名称‘x’”。这不是你所描述的发生的事情。

您所看到的实际上是 Visual Studio 2010 中的一个错误。

首先,我尝试在我的主机上重现这个问题:Win7x64 和 VS2012。对于 .NET 4.0 目标,x当它在右花括号处中断时等于 3.0D。我决定也尝试 .NET 3.5 目标,然后,x也被设置为 3.0D,不为空。

由于我在 .NET 4.0 之上安装了 .NET 4.5,我无法完美重现这个问题,因此我启动了一个虚拟机并在其上安装了 VS2010。

在这里,我能够重现该问题。在 Main 的右花括号上有一个断点方法,在watch窗口和locals窗口中,我都看到了xnull .这就是它开始变得有趣的地方。我改为针对 v2.0 运行时,发现它也在那里为空。当然不可能,因为我的另一台计算机上有相同版本的 .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)

这是 Debug模式下的正常输出。 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
操作系统线程 ID:0x135c (9)
子 SP IP 调用站点
000000001c48dc00 000007ff0017338d ConsoleApplication1.Program.Main(System.String[])
[等等……]

如果我们查看 Main 的本地机器码方法,我们可以看到在VS中断执行的时候已经运行了哪些指令:

000007ff`00173388 e813fe25f2 调用 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 添加 rsp,0D8h
000007ff`001733d6 c3 ret

使用我们从 !clrstack 获得的当前 IP在 Main ,我们看到在调用 System.Nullable<double> 后直接暂停了指令的执行。的构造函数。 ( int 3 是调试器用来停止执行的中断)我用 * 包围了该行,您也可以将该行与 L_0056 匹配在 IL。

下面的 x64 程序集实际上将它分配给局部变量 x .我们的指令指针还没有执行那段代码,所以 VS2010 在 x 之前过早地崩溃了。变量已由 native 代码分配。

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

关于c# - 双? = 双? + 双?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/13648382/

相关文章:

c# - CoInitializeSecurity 在 Visual Studio 2017 中抛出 RPC_E_TOO_LATE

c# - 处理将可变长度结果返回到固定缓冲区的方法的最有效方法是什么?

c# - 在 C# 中查找字节数组

c# - 选中组中的哪个单选按钮?

c# - 如何将Azure 2.3项目升级到Azure 2.4?

c# - 从 xml 中提取节点数据

c# - 在 try catch block 中对 IDisposable 使用 block 是否有任何问题?

c# - ffmpeg c# asp.net视频转换错误

.net - 绘制新的 System.Drawing.Bitmap

c# - 最后抛出异常。 CLR 行为与 try-catch block