Ответ Дугласа верен в отношении мертвого кода, оптимизирующего JIT ( как компиляторы x86, так и x64 будут делать это). Однако, если бы JIT-компилятор оптимизировал мертвый код, это было бы сразу очевидно, потому что xоно даже не появилось бы в окне локальных переменных. Более того, просмотр и немедленное окно вместо этого выдали бы вам ошибку при попытке доступа к нему: «Имя '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();
С точкой останова на последней фигурной скобке локальные переменные и окно просмотра отображаются 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-код, показывающий, как это происходит:
L_0045: ldloca.s CS$0$0000
L_0047: call instance !0 [mscorlib]System.Nullable`1<float64>::GetValueOrDefault()
L_004c: conv.r8
L_004d: ldloca.s CS$0$0001
L_004f: call instance !0 [mscorlib]System.Nullable`1<float64>::GetValueOrDefault()
L_0054: conv.r8
L_0055: add
L_0056: newobj instance void [mscorlib]System.Nullable`1<float64>::.ctor(!0)
L_005b: nop
L_005c: stloc.2
L_005d: ret
Давайте сделаем более глубокую отладку с помощью WinDbg:
Если вы отлаживаете приложение в VS2010 и оставляете точку останова в конце метода, мы можем легко подключить WinDbg в неинвазивном режиме.
Вот рамка для Mainметода в стеке вызовов. Мы заботимся об IP (указателе инструкции).
0: 009>! Clrstack
Идентификатор потока ОС: 0x135c (9)
Дочерний SP IP Call Site
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
Используя текущий IP , который мы получили от !clrstackв Main, мы видим , что исполнение было приостановлено по указанию непосредственно после вызова System.Nullable<double>конструктора «S. ( int 3это прерывание, используемое отладчиками для остановки выполнения) Я заключил эту строку в символ *, и вы также можете сопоставить эту строку L_0056в IL.
Следующая сборка x64 фактически назначает его локальной переменной x. Наш указатель инструкции еще не выполнил этот код, поэтому VS2010 преждевременно ломается до того, как xпеременная будет назначена собственным кодом.
РЕДАКТИРОВАТЬ: В x64 int 3инструкция помещается перед кодом назначения, как вы можете видеть выше. В x86 эта инструкция помещается после кода назначения. Это объясняет, почему VS рано ломается только в x64. Трудно сказать, виновата ли в этом Visual Studio или JIT-компилятор. Я не уверен, какое приложение вставляет перехватчики точки останова.