Ответ 1
Ответ Douglas правилен в отношении кода DIT, оптимизирующего JIT (оба компилятора x86 и x64 сделают это). Однако, если компилятор JIT оптимизирует мертвый код, это будет сразу же очевидно, потому что x
даже не появится в окне Locals. Кроме того, часы и немедленное окно вместо этого выдают вам сообщение об ошибке при попытке получить к нему доступ: "Имя" 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
, как в окне просмотра, так и в локальном окне, я увидел, что x
был null
. Здесь начинается интерес. Вместо этого я нацелился на рабочую среду 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)
Это нормальный выход в режиме отладки. 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
в Main
, мы видим, что выполнение было приостановлено в инструкции непосредственно после вызова конструктора System.Nullable<double>
. (int 3
- это прерывание, используемое отладчиками для прекращения выполнения). Я окружил эту строку с помощью *, и вы также можете сопоставить строку с L_0056
в IL.
Последовательная сборка x64 фактически присваивает ее локальной переменной x
. Наш указатель команд еще не выполнил этот код, поэтому VS2010 преждевременно ломается до того, как переменная x
была назначена нативным кодом.
EDIT: в x64 команда int 3
помещается перед кодом назначения, как вы можете видеть выше. В x86 эта команда помещается после кода назначения. Это объясняет, почему VS рано ломается только в x64. Трудно сказать, является ли это ошибкой Visual Studio или JIT-компилятора. Я не уверен, какое приложение вставляет точки прерывания.