2013-08-18 46 views
22

Tôi nghĩ rằng điều này trông giống như một lỗi trong trình biên dịch C#.Tại sao trình biên dịch đánh giá phần còn lại MinValue% -1 khác với thời gian chạy?

xem xét mã này (bên trong một phương pháp):

const long dividend = long.MinValue; 
const long divisor = -1L; 
Console.WriteLine(dividend % divisor); 

Nó biên dịch không có lỗi (hoặc cảnh báo). Có vẻ như một con bọ. Khi chạy, in 0 trên bảng điều khiển.

Sau đó, nếu không có sự const, mã:

long dividend = long.MinValue; 
long divisor = -1L; 
Console.WriteLine(dividend % divisor); 

Khi điều này được chạy, nó kết quả một cách chính xác trong một OverflowException bị ném.

Đặc tả ngôn ngữ C# đề cập đến trường hợp này một cách cụ thể và cho biết số System.OverflowException sẽ được ném. Nó không phụ thuộc vào ngữ cảnh checked hoặc unchecked có vẻ như (cũng là lỗi với toán hạng hằng số biên dịch thời gian cho toán tử còn lại là giống nhau với checkedunchecked).

Lỗi tương tự xảy ra với int (System.Int32), không chỉ long (System.Int64).

Để so sánh, trình biên dịch xử lý dividend/divisor với các toán hạng const tốt hơn nhiều so với dividend % divisor.

Câu hỏi của tôi:

Tôi có phải là lỗi này không? Nếu có, nó là một lỗi nổi tiếng mà họ không muốn sửa chữa (vì khả năng tương thích ngược, ngay cả khi nó là khá ngớ ngẩn để sử dụng % -1 với một hằng số biên dịch thời gian -1)? Hay chúng ta nên báo cáo nó để họ có thể sửa chữa nó trong các phiên bản sắp tới của trình biên dịch C#?

+0

Đề cập @EricLippert có thể thu hút đúng người cho câu hỏi này :) –

+0

@Morten, tại thời điểm này, anh ta có thể chỉ nhìn lén lút từ cá rô của mình tại Coverity. ;) –

+0

Tôi nghĩ rằng bạn nên đặt một tiền thưởng về điều này vì nó kích thích tôi tại sao điều này xảy ra. Các thông số kỹ thuật nói rằng bất kỳ biểu thức liên tục có thể ném một ngoại lệ thời gian chạy nên gây ra một lỗi biên dịch thời gian tại biên dịch !! –

Trả lời

19

Trường hợp góc này được đặc biệt giải quyết trong trình biên dịch.ý kiến ​​phù hợp nhất và mã kiểm tra vào Roslyn source:

// Although remainder and division always overflow at runtime with arguments int.MinValue/long.MinValue and -1  
// (regardless of checked context) the constant folding behavior is different.  
// Remainder never overflows at compile time while division does.  
newValue = FoldNeverOverflowBinaryOperators(kind, valueLeft, valueRight); 

Và:

// MinValue % -1 always overflows at runtime but never at compile time  
case BinaryOperatorKind.IntRemainder: 
    return (valueRight.Int32Value != -1) ? valueLeft.Int32Value % valueRight.Int32Value : 0; 
case BinaryOperatorKind.LongRemainder: 
    return (valueRight.Int64Value != -1) ? valueLeft.Int64Value % valueRight.Int64Value : 0; 

Ngoài ra hành vi của các di sản C++ phiên bản của trình biên dịch, đi tất cả các cách trở lại phiên bản 1. Từ v1 SSCLI. 0 phân phối, clr/src file nguồn/CSharp/sccomp/fncbind.cpp:

case EK_MOD: 
    // if we don't check this, then 0x80000000 % -1 will cause an exception... 
    if (d2 == -1) { 
     result = 0; 
    } else { 
     result = d1 % d2; 
    } 
    break; 

Vì vậy, kết luận rút ra rằng điều này đã không bỏ qua hoặc quên, ít nhất là bởi các lập trình viên rằng w orked trên trình biên dịch, nó có lẽ có thể được đủ điều kiện là ngôn ngữ không chính xác đủ trong các đặc tả ngôn ngữ C#. Thông tin thêm về sự cố thời gian chạy gây ra bởi kẻ giết người này đã đâm vào this post.

4

Tôi nghĩ rằng đó không phải là lỗi; nó đúng hơn là cách trình biên dịch C# tính toán % (Đó là một dự đoán). Dường như trình biên dịch C# đầu tiên tính % cho các số dương, sau đó áp dụng dấu. Có Abs(long.MinValue + 1) == Abs(long.MaxValue) nếu chúng ta viết:

static long dividend = long.MinValue + 1; 
static long divisor = -1L; 
Console.WriteLine(dividend % divisor); 

Bây giờ chúng ta sẽ thấy 0 là câu trả lời đó là chính xác bởi vì bây giờ Abs(dividend) == Abs(long.MaxValue) đó là trong phạm vi.

Tại sao lại hoạt động khi chúng tôi khai báo giá trị const sau đó? (Một lần nữa đoán) Có vẻ như trình biên dịch C# thực sự tính toán biểu thức tại thời gian biên dịch và không xem xét loại của hằng số và hành động trên nó như là một BigInteger hoặc một cái gì đó (lỗi?). Bởi vì nếu chúng tôi tuyên bố một chức năng như:

static long Compute(long l1, long l2) 
{ 
    return l1 % l2; 
} 

Và gọi Console.WriteLine(Compute(dividend, divisor)); chúng tôi sẽ có ngoại lệ tương tự. Và một lần nữa, nếu chúng tôi khai báo hằng số như sau:

const long dividend = long.MinValue + 1; 

Chúng tôi sẽ không có ngoại lệ.

+1

Tôi đã biết tất cả điều đó rồi. Lưu ý rằng spec cho biết: _Kết quả của 'x% y' là giá trị được tạo ra bởi' x - (x/y) * y'. Nếu 'y' bằng 0,' System.DivideByZeroException' được ném. ↵↵ Nếu toán hạng bên trái là giá trị 'int' hoặc' long' nhỏ nhất và toán hạng bên phải là '-1', thì' System.OverflowException' được ném ra. [...] _ Rõ ràng là từ các quan sát của bạn (và của tôi) rằng trình biên dịch không tuân thủ spec khi phần còn lại được tính toán tại thời gian biên dịch. Thời gian chạy không tuân thủ thông số kỹ thuật. –

+0

Lời xin lỗi của tôi; Tôi không đọc thông số. Vâng; Tôi đã nhìn thấy nó trong câu trả lời của tôi quá "hành động trên nó như là một BigInteger hoặc một cái gì đó (lỗi?)". Bạn nói đúng. –