2012-11-30 5 views
79

이 간단한 C# 코드로 내 마음을 잃어 버리고 있는지 여부를 알기 위해 StackOverflow 커뮤니티에 ping을하고 싶었습니다.두 배? = 두 배? + 더블?

Windows 7에서 개발 중이며 .NET 4.0, x64 Debug에서이 기능을 구현하고 있습니다. 내가 디버깅과 끝 중괄호에 중단 점을 넣으면 나는 시계 창 및 직접 실행 창에서 X = 3을 기대

static void Main() 
{ 
    double? y = 1D; 
    double? z = 2D; 

    double? x; 
    x = y + z; 
} 

:

나는 다음과 같은 코드가 있습니다. x = 대신 null.

x86에서 디버깅하는 경우 문제가없는 것 같습니다. x64 컴파일러에 문제가 있습니까? 아니면 저와 함께 뭔가 잘못 됐습니까?

+0

ideone은 모노를 실행하며 예상대로 작동합니다. http://ideone.com/DbuxwD –

+15

이것은 내 이익과 관련이 있습니다. 이제 우리는 Skeet 씨를 기다려야합니다. – MikeTheLiar

+3

디버거가 될 수 있습니까? Console.WriteLine()을 맨 끝에두고 출력 내용을 확인하십시오. – siride

답변

85

더글러스의 답변은 JIT 최적화 데드 코드 (모두 x86 및 x64 컴파일러가이 작업을 수행함)에 대한 답변입니다. 그러나 JIT 컴파일러가 불량 코드를 최적화하는 경우 x이 로컬 창에 나타나지 않기 때문에 즉시 명백하게 알 수 있습니다. 또한 시계 및 직접 실행 창은 "현재 컨텍스트에 'x'이름이 없습니다."라는 오류 메시지를 표시합니다. 그것은 당신이 일어난 것으로 묘사 한 것이 아닙니다. Win7x64 및 VS2012 :

은 당신이보고있는 것은 실제로 2010 년

먼저 Visual Studio에서 버그, 내 메인 컴퓨터에서이 문제를 재현 해 보았습니다. .NET 4.0 타겟의 경우, 닫는 중괄호가 깨질 때 x은 3.0D와 같습니다. 나는 .NET 3.5 타겟도 시도하기로 결정했고, 그걸로 x도 3.0D가 아니라 null로 설정되었다.

.NET 4.0에 .NET 4.5를 설치했기 때문에이 문제를 완벽하게 재현 할 수 없으므로 가상 컴퓨터를 설치하고 VS2010을 설치했습니다.

여기에서 문제를 재현 할 수있었습니다. 시계 창과 지역 창 둘 다에서 Main 메서드의 닫는 중괄호에 중단 점을 사용하여 xnull임을 확인했습니다. 이것은 흥미로운 곳입니다. 대신 v2.0 런타임을 대상으로하고 null도 발견했습니다.분명히 다른 컴퓨터에서 동일한 버전의 .NET 2.0 런타임을 가지고 있기 때문에 의 값이 3.0D 인 것으로 나타 났으므로 확실하지 않습니다.

그럼, 어떻게 될까요? windbg에서 파기 한 후 다음과 같은 문제를 발견했습니다.

VS2010은 실제로이 할당되기 전에 x 값을 표시합니다.

명령어 포인터가 x = y + z 라인을 넘었 기 때문에 모양이 다르다는 것을 알고 있습니다. 3.0D에 동등한 x을 보여줍니다 최종 중괄호, 지역 주민에 중단 점으로

double? y = 1D; 
double? z = 2D; 

double? x; 
x = y + z; 

Console.WriteLine(); // Don't reference x here, still leave it as dead code 

을하고 창을 시계 : 당신은 방법에 몇 줄의 코드를 추가하여이 직접 테스트 할 수 있습니다. 그러나 코드를 단계별로 실행하면 Console.WriteLine()을 통해 수행 한 이후에 VS2010에 까지 할당 된 것으로 표시되는 x이 표시되지 않습니다.

이 버그가 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 * 변수에서 해당 변수를 사용자 정의 로컬 변수로 복사합니다. 당신이 VS2010에서 응용 프로그램을 디버깅 및 방법의 말, 우리가 할 수에서 브레이크 포인트를두면

:

// 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는 몇 가지 더 깊은 디버깅을하자 : 여기에 이런 일을 보여줍니다 해당 IL 코드는 비 침습적 인 모드에서 WinDbg를 쉽게 부착하십시오.

다음은 호출 스택의 Main 메서드에 대한 프레임입니다. 우리는 IP (instruction pointer)에 관심이있다.

 
0:009> !clrstack 
OS Thread Id: 0x135c (9) 
Child SP   IP    Call Site 
000000001c48dc00 000007ff0017338d ConsoleApplication1.Program.Main(System.String[]) 
[And so on...] 

우리가 Main 방법에 대한 기본 시스템 코드를 볼 경우, 우리는 지시 VS 실행이 중단되는 시간에 실행 된 것을 볼 수있다 : 우리가에서 가져온 현재 IP를 사용

 
000007ff`00173388 e813fe25f2  call 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 add  rsp,0D8h 
000007ff`001733d6 c3    ret 

!clrstackMain에 넣었을 때, 의 직후에 실행이 중단되었고, System.Nullable<double>의 생성자가 호출 된 것을 볼 수 있습니다.(int 3은 디버거가 실행을 중지하는 데 사용하는 인터럽트입니다.) 나는 그 줄을 *로 둘러 쌌으며 IL의 L_0056 줄까지 일치시킬 수도 있습니다.

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

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

+4

+1 : 그럴듯 해 보인다. 감사! – Douglas

+6

@ChristopherCurrens 알았어, 녹색 확인 표시가 생겼어. 이것은 의미가 있습니다. 나는 뭔가를 마이크로 소프트에 보낼 것이다. 모두에게 관심을 가져 주셔서 감사합니다. 지역 사회의 노력에 깊은 인상을 받았습니다. – MrE

30

x64 JIT 컴파일러는 x86보다 최적화면에서 공격적인 것으로 알려져 있습니다. (x86 및 x64 컴파일러가 의미 상으로 다른 코드를 생성하는 경우 "Array Bounds Check Elimination in the CLR"을 참조 할 수 있습니다.)

이 경우 x64 컴파일러는 x이 절대로 읽히지 않는 것을 감지하고 할당을 제거합니다 전부; 이것은 컴파일러 최적화에서 dead code elimination으로 알려져 있습니다. 바로 할당 한 후 다음 줄을 추가 문제가 발생하지 않도록하려면

Console.WriteLine(x); 

당신은 3의 정확한 값이 인쇄됩니까뿐만 아니라 관찰되지만, 변수 x 디버거에서 올바른 값을 표시 할 또한 (편집)을 참조하는 Console.WriteLine 호출 다음에 있습니다.

: Christopher Currens는 Visual Studio 2010의 버그를 가리키는 alternative explanation을 제공합니다 (위보다 정확할 수 있음).

+0

어, 이건 내 첫 번째 생각이기 때문에 그냥 테스트 해봤는데이 대답은 실제로 잘못되었습니다. 디버거는'Console.WriteLine()'을 호출 할 때까지 할당 후에 실제로 null을 표시합니다. – tomfanning

+0

@tomfanning : 그 대답을 무효화하지 않습니다. 컴파일러는 'Console.WriteLine'에서 그러한 것이 필요할 것임을 감지 한 시점에서만 할당을 수행하기위한 지시를 내 보냅니다. – Douglas

+0

필자는 컴파일러가 다른 것 (x86 대 64 비트)을 의심합니다. 여기서 볼 수있는 것은 해제되지 않은 JIT 최적화입니다. – leppie