Tại sao giá trị liệt kê từ một mảng đa chiều không bằng chính nó?


151

Xem xét:

using System;

public class Test
{
    enum State : sbyte { OK = 0, BUG = -1 }

    static void Main(string[] args)
    {
        var s = new State[1, 1];
        s[0, 0] = State.BUG;
        State a = s[0, 0];
        Console.WriteLine(a == s[0, 0]); // False
    }
}

làm như thế nào để giải thích chuyện này? Nó xảy ra trong các bản dựng gỡ lỗi trong Visual Studio 2015 khi chạy trong x86 JIT. Bản dựng phát hành hoặc chạy trong J64 x64 in đúng như mong đợi.

Để sao chép từ dòng lệnh:

csc Test.cs /platform:x86 /debug

( /debug:pdbonly, /debug:portable/debug:fullcũng sinh sản.)


2
ideone.com/li3EzY đó là sự thật. thêm thông tin về phiên bản .net, IDE, trình biên dịch
Sao lưu

1
Tương tự ở đây. Nhưng sau khi thay đổi cài đặt dự án, tôi phát hiện ra rằng việc bỏ chọn hộp kiểm "Thích 32 bit" trong tab "Xây dựng" làm cho nó hoạt động như dự định - trả về đúng. Vì vậy, nó trông giống như một vấn đề WoW64 đối với tôi.
Dmitry Rotay

2
Có vẻ như bạn đã chỉ ra một lỗi trong khung.
Fabien PERRONNET

1
Thật thú vị, chạy mã bị hỏng thông qua ildasmvà sau đó ilasm"sửa" nó ...
Jon Skeet

2
Các /debug=IMPLlá cờ nó bị phá vỡ; /debug=OPT"sửa" nó.
Jon Skeet

Câu trả lời:


163

Bạn đã tìm thấy một lỗi tạo mã trong jitter .NET 4 x86. Đó là một điều rất bất thường, nó chỉ thất bại khi mã không được tối ưu hóa. Mã máy trông như thế này:

        State a = s[0, 0];
013F04A9  push        0                            ; index 2 = 0
013F04AB  mov         ecx,dword ptr [ebp-40h]      ; s[] reference
013F04AE  xor         edx,edx                      ; index 1 = 0
013F04B0  call        013F0058                     ; eax = s[0, 0]
013F04B5  mov         dword ptr [ebp-4Ch],eax      ; $temp1 = eax 
013F04B8  movsx       eax,byte ptr [ebp-4Ch]       ; convert sbyte to int
013F04BC  mov         dword ptr [ebp-44h],eax      ; a = s[0, 0]
        Console.WriteLine(a == s[0, 0]); // False
013F04BF  mov         eax,dword ptr [ebp-44h]      ; a
013F04C2  mov         dword ptr [ebp-50h],eax      ; $temp2 = a
013F04C5  push        0                            ; index 2 = 0
013F04C7  mov         ecx,dword ptr [ebp-40h]      ; s[] reference 
013F04CA  xor         edx,edx                      ; index 1 = 0
013F04CC  call        013F0058                     ; eax = s[0, 0]
013F04D1  mov         dword ptr [ebp-54h],eax      ; $temp3 = eax 
                                               ; <=== Bug here!
013F04D4  mov         eax,dword ptr [ebp-50h]      ; a == s[0, 0] 
013F04D7  cmp         eax,dword ptr [ebp-54h]  
013F04DA  sete        cl  
013F04DD  movzx       ecx,cl  
013F04E0  call        731C28F4  

Một vấn đề khó khăn với rất nhiều thời gian và sao chép mã, đó là bình thường đối với mã không được tối ưu hóa. Hướng dẫn tại 013F04B8 là đáng chú ý, đó là nơi xảy ra sự chuyển đổi cần thiết từ sbyte sang số nguyên 32 bit. Hàm trợ giúp mảng getter trả về 0x0000000FF, bằng State.BUG và cần được chuyển đổi thành -1 (0xFFFFFFFF) trước khi có thể so sánh giá trị. Lệnh MOVSX là một lệnh Sign eXtension.

Điều tương tự lại xảy ra một lần nữa tại 013F04CC, nhưng lần này không có hướng dẫn MOVSX để thực hiện chuyển đổi tương tự. Đó là nơi các chip rơi xuống, hướng dẫn CMP so sánh 0xFFFFFFFF với 0x000000FF và đó là sai. Vì vậy, đây là một lỗi thiếu sót, trình tạo mã không thể phát lại MOVSX để thực hiện cùng một sbyte để chuyển đổi int.

Điều đặc biệt khác thường về lỗi này là lỗi này hoạt động chính xác khi bạn kích hoạt trình tối ưu hóa, giờ đây nó đã biết sử dụng MOVSX trong cả hai trường hợp.

Lý do có thể xảy ra là lỗi này không bị phát hiện quá lâu là việc sử dụng sbyte làm loại cơ sở của enum. Khá hiếm để làm. Sử dụng một mảng đa chiều cũng là công cụ, sự kết hợp gây tử vong.

Nếu không, một lỗi khá nghiêm trọng tôi muốn nói. Làm thế nào rộng rãi có thể khó đoán, tôi chỉ có jitter 4.6.1 x86 để kiểm tra. Các jitter x64 và 3,5 x86 tạo mã rất khác nhau và tránh lỗi này. Cách giải quyết tạm thời để tiếp tục là loại bỏ sbyte làm loại cơ sở enum và để nó là mặc định, int , do đó không cần mở rộng dấu hiệu.

Bạn có thể gửi lỗi tại connect.microsoft.com, liên kết đến Q + A này là đủ để nói với họ mọi thứ họ cần biết. Hãy cho tôi biết nếu bạn không muốn dành thời gian và tôi sẽ chăm sóc nó.


33
Dữ liệu tốt, vững chắc với nguyên nhân chính xác của một vấn đề kỳ lạ như vậy, luôn luôn là một niềm vui để đọc, +1.
Lasse V. Karlsen

11
Vui lòng gửi một liên kết đến bài viết Connect.microsoft.com để chúng tôi có thể bỏ phiếu cho nó.
Hans Passant

Tôi giả sử sử dụng bytethay vì sbytecũng sẽ ổn và có thể tốt hơn nếu mã thực được sử dụng với ORM mà bạn không muốn cờ của mình trong cơ sở dữ liệu chiếm thêm dung lượng.
Voo

6
Tôi sẽ đăng lỗi trên dotnet / coreclr thay vì kết nối, bạn sẽ truy cập thẳng vào các nhà phát triển JIT.
Lucas Trzesniewski

8
Tôi là một nhà phát triển trong nhóm JIT tại Microsoft. Tôi đã sao chép lỗi và đã mở một vấn đề cho nó trong nội bộ (vận chuyển x86 JIT chưa được mở trên github). Về mặt thời gian khi điều này sẽ được khắc phục, tôi dự đoán rằng chúng ta sẽ có bản sửa lỗi này trong bản phát hành chính tiếp theo của các công cụ. Nếu lỗi này có tác động kinh doanh và bạn cần khắc phục sớm hơn, vui lòng gửi sự cố kết nối (connect.microsoft.com) để chúng tôi có thể xem xét tác động và những giải pháp thay thế nào chúng tôi phải khắc phục cho bạn nhanh hơn.
Russell C. Hadley

8

Hãy xem xét tuyên bố của OP:

enum State : sbyte { OK = 0, BUG = -1 }

Vì lỗi chỉ xảy ra khi BUGâm (từ -128 đến -1) và Trạng thái là một enum của byte đã ký, tôi bắt đầu cho rằng có một vấn đề nào đó xảy ra ở đâu đó.

Nếu bạn chạy này:

Console.WriteLine((sbyte)s[0, 0]);
Console.WriteLine((sbyte)State.BUG);
Console.WriteLine(s[0, 0]);
unchecked
{
    Console.WriteLine((byte) State.BUG);
}

nó sẽ xuất ra:

255

-1

BỌ CÁNH CỨNG

255

Vì một lý do mà tôi bỏ qua (tính đến thời điểm hiện tại) s[0, 0] được chuyển thành một byte trước khi đánh giá và đó là lý do tại sao nó tuyên bố đó a == s[0,0]là sai.

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 cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.