[c#] 더블? = 더블? + 더블?

이 간단한 C # 코드로 마음을 잃고 있는지 여부를 확인하기 위해 StackOverflow 커뮤니티를 ping하고 싶었습니다.

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 컴파일러에 문제가 있거나 나에게 문제가 있습니까?



답변

Douglas의 대답은 JIT 최적화 데드 코드에 대한 정답입니다 ( x86 및 x64 컴파일러 모두이 작업을 수행합니다). 그러나 JIT 컴파일러가 데드 코드를 최적화 x하면 Locals 창에도 나타나지 않기 때문에 즉시 명백 합니다. 또한, 조사 식 및 직접 실행 창에 액세스하려고 할 때 “현재 컨텍스트에 ‘x’이름이 없습니다.”라는 오류가 표시됩니다. 그것은 당신이 일어난 일이라고 묘사 한 것이 아닙니다.

지금보고있는 것은 실제로 Visual Studio 2010의 버그입니다.

먼저,이 문제를 주 컴퓨터 인 Win7x64 및 VS2012에서 재현하려고했습니다. .NET 4.0 대상의 x경우 닫는 중괄호에서 끊기면 3.0D와 같습니다. 나는 .NET 3.5 타겟도 시도하기로 결정했고, x그것도 null이 아닌 3.0D로 설정되었습니다.

.NET 4.0 위에 .NET 4.5를 설치했기 때문에이 문제를 완벽하게 재현 할 수 없기 때문에 가상 머신을 가동하고 VS2010을 설치했습니다.

여기에서 문제를 재현 할 수있었습니다. 의 닫는 중괄호에 중단 점으로 Main조사 식 창 및 지역 창, I 톱 모두의 방법 x이었다 null. 이것이 흥미로워지기 시작하는 곳입니다. 대신 v2.0 런타임을 대상으로했고 거기에서도 null임을 발견했습니다. .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 스레드 ID : 0x135c (9)
하위 SP IP 호출 사이트
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>의 생성자입니다. ( int 3디버거가 실행을 중지하는 데 사용하는 인터럽트입니다.) 해당 줄을 *로 둘러 쌌 L_0056으며 IL에서 줄을 일치시킬 수도 있습니다 .

뒤에 나오는 x64 어셈블리는 실제로이 어셈블리를 로컬 변수에 할당합니다 x. 우리의 명령어 포인터는 아직 해당 코드를 실행하지 않았기 때문에 VS2010은 x변수가 네이티브 코드에 의해 할당 되기 전에 조기에 중단 됩니다.

편집 : x64에서는 int 3위에서 볼 수 있듯이 명령이 할당 코드 앞에 배치됩니다. x86에서 해당 명령어는 할당 코드 뒤에 배치됩니다. 이것이 VS가 x64에서만 일찍 중단되는 이유를 설명합니다. 이것이 Visual Studio 또는 JIT 컴파일러의 결함인지 말하기는 어렵습니다. 어떤 응용 프로그램이 중단 점 후크를 삽입하는지 잘 모르겠습니다.


답변

x64 JIT 컴파일러는 x86보다 최적화에서 더 공격적인 것으로 알려져 있습니다. ( x86 및 x64 컴파일러가 의미가 다른 코드를 생성하는 경우 ” CLR에서 배열 경계 검사 제거 “를 참조 할 수 있습니다 .)

이 경우 x64 컴파일러는 x결코 읽히지 않는 것을 감지 하고 할당을 완전히 제거합니다. 이것은 컴파일러 최적화에서 데드 코드 제거 로 알려져 있습니다. 이러한 일이 발생하지 않도록하려면 할당 바로 뒤에 다음 줄을 추가하십시오.

Console.WriteLine(x);

3get 의 올바른 값을 인쇄 x할 뿐만 아니라 변수 가 이를 참조 하는 호출 이후에도 디버거에 올바른 값을 표시 (편집) 하는 것을 관찰 할 수 Console.WriteLine있습니다.

편집 : Christopher Currens는 Visual Studio 2010의 버그를 가리키는 대체 설명을 제공합니다 . 이는 위보다 더 정확할 수 있습니다.


답변