gấp đôi? = gấp đôi? + gấp đôi?


79

Tôi muốn ping cộng đồng StackOverflow để xem liệu tôi có mất trí hay không với đoạn mã C # đơn giản này.

Tôi đang phát triển trên Windows 7, xây dựng cái này trong .NET 4.0, x64 Debug.

Tôi có mã sau:

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

    double? x;
    x = y + z;
}

Nếu tôi gỡ lỗi và đặt một điểm ngắt trên dấu ngoặc nhọn kết thúc, tôi mong đợi x = 3 trong Cửa sổ xem và Cửa sổ ngay lập tức. x = null thay vào đó.

Nếu tôi gỡ lỗi trong x86, mọi thứ dường như hoạt động tốt. Có vấn đề gì với trình biên dịch x64 hoặc có vấn đề gì với tôi không?


15
Điều này có liên quan đến lợi ích của tôi. Bây giờ chúng ta chỉ cần đợi ông Skeet.
— Mike G

3
Nó có thể là trình gỡ lỗi? Hãy thử đặt Console.WriteLine () ở cuối và xem những gì nó in ra.
— siride

3
Điều này trông giống như một quá trình thực thi bị trì hoãn mà trình gỡ lỗi không thể nhìn thấy được. Nhiệm vụ có thể không được thực hiện cho đến khi nó cần được thực hiện.
— Joel Etherton

5
@igrimpe Rõ ràng 1 + 2 = 25 cho các giá trị rất lớn của 1 và 2?
— Chris Sinclair,

2
Cảm ơn sự phản hồi của mọi người. Khá tuyệt khi thấy cộng đồng nhảy vào điều này một cách nhanh chóng. Hành vi của trình biên dịch x64 làm cho việc gỡ lỗi các loại câu lệnh này hơi không trực quan theo ý kiến ​​của tôi, nhưng ít nhất có vẻ như việc thực thi mã hoạt động đúng trong lược đồ lớn của chính ứng dụng.
— MrE

Câu trả lời:


86

Câu trả lời của Douglas là đúng về mã chết tối ưu hóa JIT ( cả trình biên dịch x86 và x64 sẽ làm điều này). Tuy nhiên, nếu trình biên dịch JIT đang tối ưu hóa mã chết thì điều đó sẽ hiển nhiên ngay lập tức vì xthậm chí sẽ không xuất hiện trong cửa sổ Người dân địa phương. Hơn nữa, thay vào đó, cửa sổ xem và ngay lập tức sẽ cung cấp cho bạn một lỗi khi cố gắng truy cập nó: "Tên 'x' không tồn tại trong ngữ cảnh hiện tại". Đó không phải là những gì bạn đã mô tả là đang xảy ra.

Những gì bạn đang thấy thực sự là một lỗi trong Visual Studio 2010.

Đầu tiên, tôi đã cố gắng tạo lại sự cố này trên máy chính của mình: Win7x64 và VS2012. Đối với các mục tiêu .NET 4.0, xbằng 3.0D khi nó ngắt trên dấu ngoặc nhọn đóng. Tôi cũng quyết định thử các mục tiêu .NET 3.5 và cùng với đó, xcũng được đặt thành 3.0D, không phải null.

Vì tôi không thể tái tạo hoàn hảo vấn đề này vì tôi đã cài đặt .NET 4.5 trên .NET 4.0, tôi đã tạo một máy ảo và cài đặt VS2010 trên đó.

Tại đây, tôi đã có thể tái tạo vấn đề. Với một điểm ngắt trên dấu ngoặc nhọn đóng của Mainphương thức, trong cả cửa sổ theo dõi và cửa sổ định vị, tôi thấy điều đó xlà đúng null. Đây là nơi mà nó bắt đầu trở nên thú vị. Thay vào đó, tôi đã nhắm mục tiêu thời gian chạy v2.0 và thấy rằng nó cũng không có giá trị ở đó. Chắc chắn đó không thể là trường hợp vì tôi có cùng một phiên bản thời gian chạy .NET 2.0 trên máy tính khác của tôi đã hiển thị thành công xvới giá trị là 3.0D.

Vậy, chuyện gì đang xảy ra vậy? Sau một số tìm hiểu trong windbg, tôi đã tìm thấy vấn đề:

VS2010 đang hiển thị cho bạn giá trị của x trước khi nó thực sự được gán .

Tôi biết đó không phải là những gì nó trông như thế nào, vì con trỏ hướng dẫn ở quá x = y + zdòng. Bạn có thể tự kiểm tra điều này bằng cách thêm một vài dòng mã vào phương pháp:

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

double? x;
x = y + z;

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

Với một điểm ngắt trên dấu ngoặc nhọn cuối cùng, các địa phương và cửa sổ đồng hồ hiển thị xbằng 3.0D. Tuy nhiên, nếu bạn bước qua mã, bạn sẽ nhận thấy rằng VS2010 không hiển thị xlà được chỉ định cho đến khi bạn bước qua Console.WriteLine().

Tôi không biết liệu lỗi này đã từng được báo cáo với Microsoft Connect chưa, nhưng bạn có thể muốn làm điều đó, với mã này làm ví dụ. Tuy nhiên, rõ ràng nó đã được sửa trong VS2012, vì vậy tôi không chắc liệu sẽ có bản cập nhật để sửa lỗi này hay không.


Đây là những gì đang thực sự xảy ra trong JIT và VS2010

Với mã gốc, chúng ta có thể thấy VS đang làm gì và tại sao nó lại sai. Chúng tôi cũng có thể thấy rằng xbiến không được tối ưu hóa (trừ khi bạn đã đánh dấu assembly là được biên dịch với tối ưu hóa được bật).

Trước tiên, hãy xem xét các định nghĩa biến cục bộ của 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)

Đây là đầu ra bình thường ở chế độ gỡ lỗi. Visual Studio xác định các biến cục bộ trùng lặp sử dụng trong quá trình gán và sau đó thêm các lệnh IL bổ sung để sao chép nó từ biến CS * sang biến cục bộ tương ứng do người dùng xác định. Đây là mã IL tương ứng cho thấy điều này đang xảy ra:

// 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 

Hãy thực hiện một số gỡ lỗi sâu hơn với WinDbg:

Nếu bạn gỡ lỗi ứng dụng trong VS2010 và để lại một điểm ngắt ở cuối phương pháp, chúng ta có thể đính kèm WinDbg một cách dễ dàng, ở chế độ không xâm lấn.

Đây là khung cho Mainphương thức trong ngăn xếp cuộc gọi. Chúng tôi quan tâm đến IP (con trỏ hướng dẫn).

0: 009>! Clrstack
Id chuỗi hệ điều hành: 0x135c (9)
Trang web cuộc gọi IP SP con
000000001c48dc00 000007ff0017338d ConsoleApplication1.Program.Main (System.String [])
[Và như thế...]

Nếu chúng tôi xem mã máy gốc cho Mainphương thức, chúng tôi có thể xem những hướng dẫn nào đã được chạy tại thời điểm VS phá vỡ thực thi:

000007ff`00173388 e813fe25f2 gọi 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 rcx bước, [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 movap xmm6, xmmword ptr [rsp + 0C0h]
000007ff`001733cf 4881c4d8000000 thêm rsp, 0D8h
000007ff`001733d6 c3 ret

Sử dụng IP hiện tại mà chúng ta nhận được từ !clrstacktrong Main, chúng ta thấy thực hiện mà đã bị đình chỉ trên hướng dẫn trực tiếp sau khi cuộc gọi đến System.Nullable<double>của constructor. ( int 3là ngắt được sử dụng bởi trình gỡ lỗi để ngừng thực thi) Tôi đã bao quanh dòng đó bằng * 's và bạn cũng có thể so khớp dòng với L_0056trong IL.

Hợp ngữ x64 theo sau thực sự gán nó cho biến cục bộ x. Con trỏ hướng dẫn của chúng tôi chưa thực thi mã đó, vì vậy VS2010 sớm bị hỏng trước khi xbiến được gán bởi mã gốc.

CHỈNH SỬA: Trong x64, int 3lệnh được đặt trước mã gán, như bạn có thể thấy ở trên. Trong x86, lệnh đó được đặt sau mã gán. Điều đó giải thích tại sao VS chỉ phá vỡ sớm trong x64. Thật khó để nói đây là lỗi của Visual Studio hay trình biên dịch JIT. Tôi không chắc ứng dụng nào chèn móc ngắt điểm.


6
@ChristopherCurrens Được rồi, bạn đã kiếm được dấu kiểm màu xanh lục. Điều này thật ý nghĩa. Tôi sẽ gửi một cái gì đó vào Microsoft. Cảm ơn mọi người đã chú ý đến điều này. Rất ấn tượng về nỗ lực của cộng đồng.
— MrE

30

Trình biên dịch x64 JIT được biết là tích cực hơn trong việc tối ưu hóa nó so với x86. (Bạn có thể tham khảo “ Loại bỏ kiểm tra giới hạn mảng trong CLR ” để biết trường hợp trình biên dịch x86 và x64 tạo mã khác nhau về ngữ nghĩa.)

Trong trường hợp này, trình biên dịch x64 đang phát hiện ra rằng xkhông bao giờ được đọc và loại bỏ hoàn toàn nhiệm vụ của nó; điều này được gọi là loại bỏ mã chết trong tối ưu hóa trình biên dịch. Để ngăn điều này xảy ra, chỉ cần thêm dòng sau ngay sau nhiệm vụ:

Console.WriteLine(x);

Bạn sẽ thấy rằng không chỉ giá trị chính xác của 3được in ra, mà biến xcũng sẽ hiển thị giá trị chính xác trong trình gỡ lỗi (chỉnh sửa) sau lệnh Console.WriteLinegọi tham chiếu đến nó.

Chỉnh sửa : Christopher Currens đưa ra lời giải thích thay thế chỉ ra một lỗi trong Visual Studio 2010, có thể chính xác hơn phần trên.


Ơ, tôi vừa thử nghiệm điều này vì đây cũng là suy nghĩ đầu tiên của tôi, và câu trả lời này thực sự không chính xác. Trình gỡ lỗi thực sự hiển thị null sau khi gán cho đến sau cuộc gọi tới Console.WriteLine().
— tomfanning

@tomfanning: Điều đó không làm mất hiệu lực của câu trả lời. Trình biên dịch chỉ phát ra lệnh để thực hiện nhiệm vụ tại điểm mà nó phát hiện ra rằng điều đó sẽ cần thiết - tại Console.WriteLine.
— Douglas

1
@leppie Trình biên dịch là (hoặc ít nhất, đã từng là) khác nhau. Ví dụ, nó sẽ nội tuyến mạnh hơn nhiều so với trình biên dịch x86.
— Konrad Rudolph

2
@leppie: “ Array Bounds Check Elimination trong CLR ” đưa ra các ví dụ về nơi mà các trình biên dịch có thể thực sự dẫn đến sự khác biệt về ngữ nghĩa. “Các trình biên dịch JIT cho x86 và x64 hiện là các cơ sở mã khá khác nhau […] JIT x86 nhanh hơn về tốc độ biên dịch; JIT x64 chậm hơn, nhưng thực hiện nhiều tối ưu hóa thú vị hơn. ”
— Douglas

2
@MrE - Đây không nên được đánh dấu là câu trả lời đúng. Nếu trình biên dịch JIT đang tối ưu hóa mã chết, visual studio sẽ không thể giải quyết xbiến. Nó sẽ không xuất hiện trong cửa sổ người dân địa phương, và cố gắng để xem nó trong chiếc đồng hồ hoặc các cửa sổ ngay lập tức sẽ cung cấp cho các thông điệp: The name 'x' does not exist in the current context.
— Christopher Currens vào
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookie và Chính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.