Я хотел связаться с сообществом 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
оно даже не появилось бы в окне локальных переменных. Более того, просмотр и немедленное окно вместо этого выдали бы вам ошибку при попытке доступа к нему: «Имя '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 (указателе инструкции).Если мы
Main
посмотрим на собственный машинный код метода, мы увидим, какие инструкции были выполнены в момент, когда VS прерывает выполнение:Используя текущий 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-компилятор. Я не уверен, какое приложение вставляет перехватчики точки останова.источник
Компилятор x64 JIT, как известно, более агрессивен в оптимизации, чем x86. (Вы можете обратиться к разделу «Устранение проверки границ массива в CLR » в случае, если компиляторы x86 и x64 генерируют код, который семантически отличается.)
В этом случае компилятор x64 обнаруживает, что
x
никогда не читается, и полностью отменяет его назначение; это известно как устранение мертвого кода при оптимизации компилятора. Чтобы этого не произошло, просто добавьте следующую строку сразу после задания:Вы заметите, что не только
3
печатается правильное значение , но и переменнаяx
отображает правильное значение в отладчике (редактировать) послеConsole.WriteLine
вызова, который на нее ссылается.Изменить : Кристофер Карренс предлагает альтернативное объяснение, указывающее на ошибку в Visual Studio 2010, которое может быть более точным, чем приведенное выше.
источник
Console.WriteLine()
.Console.WriteLine
.x
переменную. Это не будет появляться в окне местных жителей, и пытается его просмотр в часах или немедленные окна дадут сообщение:The name 'x' does not exist in the current context
.