Odpowiedź Douglasa jest prawidłowa, jeśli chodzi o optymalizację martwego kodu JIT ( zrobią to zarówno kompilatory x86, jak i x64). Jednak gdyby kompilator JIT optymalizował martwy kod, byłoby to od razu oczywiste, ponieważ xnie pojawiłby się nawet w oknie Locals. Co więcej, okienko obserwacyjne i bezpośrednie wyświetlałyby błąd podczas próby uzyskania do niego dostępu: „Nazwa 'x' nie istnieje w bieżącym kontekście”. To nie jest to, co opisałeś jako się dzieje.
To, co widzisz, jest w rzeczywistości błędem w programie Visual Studio 2010.
Najpierw próbowałem odtworzyć ten problem na moim głównym komputerze: Win7x64 i VS2012. W przypadku obiektów docelowych .NET 4.0 xjest równa 3,0D, gdy przerywa w zamykającym nawiasie klamrowym. Zdecydowałem się również wypróbować cele .NET 3.5, a wraz z nim xrównież został ustawiony na 3.0D, a nie null.
Ponieważ nie mogę wykonać perfekcyjnej reprodukcji tego problemu, ponieważ mam .NET 4.5 zainstalowane na .NET 4.0, uruchomiłem maszynę wirtualną i zainstalowałem na niej VS2010.
Tutaj udało mi się odtworzyć problem. Z punktem przerwania na zamykającym nawiasie klamrowym Mainmetody, zarówno w oknie zegarka, jak i oknie lokalnych, zobaczyłem, że xtak null. Tutaj zaczyna się robić ciekawie. Zamiast tego wybrałem środowisko uruchomieniowe v2.0 i stwierdziłem, że tam też było zerowe. Z pewnością nie może tak być, ponieważ na innym komputerze mam tę samą wersję środowiska uruchomieniowego .NET 2.0, która pomyślnie wyświetlała się xz wartością 3.0D.
Więc co się dzieje? Po pewnym kopaniu w windbg znalazłem problem:
VS2010 pokazuje wartość x przed faktycznym przypisaniem .
Wiem, że nie tak to wygląda, ponieważ wskaźnik instrukcji znajduje się za x = y + zlinią. Możesz to sprawdzić samodzielnie, dodając kilka wierszy kodu do metody:
double? y = 1D;
double? z = 2D;
double? x;
x = y + z;
Console.WriteLine();
Z punktem przerwania na ostatnim nawiasie klamrowym, dane lokalne i okno zegarka xsą równe 3.0D. Jeśli jednak krok za pomocą kodu, można zauważyć, że VS2010 nie pokazuje xjako przydzielony aż po już przeszedł przez Console.WriteLine().
Nie wiem, czy ten błąd został kiedykolwiek zgłoszony do Microsoft Connect, ale możesz to zrobić, używając tego kodu jako przykładu. Wyraźnie zostało to jednak naprawione w VS2012, więc nie jestem pewien, czy będzie aktualizacja, która to naprawi, czy nie.
Oto, co faktycznie dzieje się w JIT i VS2010
Dzięki oryginalnemu kodowi możemy zobaczyć, co robi VS i dlaczego jest źle. Widzimy również, że xzmienna nie jest optymalizowana (chyba że zaznaczyłeś zestaw do kompilacji z włączonymi optymalizacjami).
Najpierw przyjrzyjmy się definicjom zmiennych lokalnych w 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)
Jest to normalne wyjście w trybie debugowania. Program Visual Studio definiuje zduplikowane zmienne lokalne, które są używane podczas przypisań, a następnie dodaje dodatkowe polecenia IL, aby skopiować je ze zmiennej CS * do odpowiedniej zmiennej lokalnej zdefiniowanej przez użytkownika. Oto odpowiedni kod IL, który pokazuje, że to się dzieje:
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
Zróbmy głębsze debugowanie za pomocą WinDbg:
Jeśli debugujesz aplikację w VS2010 i zostawisz punkt przerwania na końcu metody, możemy łatwo dołączyć WinDbg, w trybie nieinwazyjnym.
Oto ramka dla Mainmetody w stosie wywołań. Dbamy o adres IP (wskaźnik instrukcji).
0: 009>! Clrstack
Identyfikator wątku systemu operacyjnego: 0x135c (9)
Witryna wywołań IP dziecka SP
000000001c48dc00 000007ff0017338d ConsoleApplication1.Program.Main (System.String [])
[I tak dalej...]
Jeśli Mainprzejrzymy natywny kod maszynowy metody, możemy zobaczyć, jakie instrukcje zostały uruchomione w momencie, gdy VS przerywa wykonanie:
000007ff`00173388 e813fe25f2 zadzwoń do 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 dodaj rsp, 0D8h
000007ff`001733d6 c3 ret
Korzystanie aktualny adres IP, który dostaliśmy od !clrstackw Main, widzimy, że wykonanie zostało zawieszone na zlecenie bezpośrednio po wywołaniu System.Nullable<double>„s konstruktora. ( int 3jest przerwaniem używanym przez debuggery do zatrzymania wykonywania). Otoczyłem tę linię znakami * i możesz również dopasować linię do L_0056w IL.
Poniższy zestaw x64 faktycznie przypisuje go do zmiennej lokalnej x. Nasz wskaźnik instrukcji nie wykonał jeszcze tego kodu, więc VS2010 przedwcześnie się psuje, zanim xzmienna zostanie przypisana przez kod natywny.
EDYCJA: W x64 int 3instrukcja jest umieszczana przed kodem przydziału, jak widać powyżej. W x86 ta instrukcja jest umieszczana po kodzie przypisania. To wyjaśnia, dlaczego VS psuje się wcześnie tylko w x64. Trudno powiedzieć, czy to wina programu Visual Studio, czy kompilatora JIT. Nie jestem pewien, która aplikacja wstawia punkty przerwania.