Відповідь Дугласа правильно про JIT оптимізуючи мертвий код ( як x86 і x64 компіляторів будуть робити це). Однак, якби компілятор JIT оптимізував мертвий код, це було б відразу очевидно, оскільки xнавіть не з'являлося б у вікні Locals. Крім того, годинник і безпосереднє вікно замість цього видадуть вам помилку при спробі отримати до нього доступ: "Ім'я 'x' не існує в поточному контексті". Це не те, що ви описали як те, що відбувається.
Те, що ви бачите, насправді є помилкою у Visual Studio 2010.
Спочатку я спробував відтворити цю проблему на своїй головній машині: Win7x64 та VS2012. Для цілей .NET 4.0 xдорівнює 3.0D при розриві фігурної фігурної дужки. Я вирішив спробувати також цілі .NET 3.5, і з цим,x також було встановлено значення 3.0D, а не нуль.
Оскільки я не можу досконало відтворити цю проблему, оскільки у мене .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)
Сайт ІР-дзвінків дочірнього СП
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 рет
Використовуючи поточний IP, який ми отримали !clrstackв Main, ми бачимо, що виконання було призупинено за інструкцією безпосередньо після виклику System.Nullable<double>конструктора. ( int 3це переривання, яке використовують відладчики для зупинки виконання). Я оточив цей рядок знаками *, і ви також можете зрівняти рядок L_0056з ІЛ.
Наступна збірка x64 насправді призначає її локальній змінній x. Наш покажчик інструкцій ще не виконав цей код, тому VS2010 передчасно переривається до того, як xзмінній буде призначено власний код.
EDIT: У x64 int 3інструкція розміщується перед кодом призначення, як ви можете бачити вище. У x86 ця інструкція розміщується після коду призначення. Це пояснює, чому VS пробивається рано лише в x64. Важко сказати, чи це винна Visual Studio або компілятор JIT. Я не впевнений, яка програма вставляє гачки точки зупинку.