22

我认为这看起来像是 C# 编译器中的一个错误。

考虑这段代码(在方法内):

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

它编译时没有错误(或警告)。似乎是一个错误。0运行时,在控制台上打印。

然后没有const,代码:

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

当它运行时,它会正确地导致OverflowException被抛出。

C# 语言规范特别提到了这种情况,并说 aSystem.OverflowException应该被抛出。它不依赖于上下文checkedunchecked看起来(编译时常量操作数对余数运算符的错误与checkedand相同unchecked)。

int( )会发生同样的错误System.Int32,而不仅仅是long( System.Int64)。

相比之下,编译器dividend / divisorconst操作数的处理比dividend % divisor.

我的问题:

我是对的,这是一个错误吗?如果是,这是他们不希望修复的众所周知的错误(因为向后兼容性,即使% -1与编译时常量一起使用是相当愚蠢的-1)?或者我们应该报告它,以便他们可以在即将发布的 C# 编译器版本中修复它?

4

2 回答 2

19

This corner-case is very specifically addressed in the compiler. Most relevant comments and code in the 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);

And:

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

Also the behavior of the legacy C++ version of compiler, going all the way back to version 1. From the SSCLI v1.0 distribution, clr/src/csharp/sccomp/fncbind.cpp source file:

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;

So conclusion to draw that this was not overlooked or forgotten about, at least by the programmers that worked on the compiler, it could perhaps be qualified as insufficiently precise language in the C# language specification. More about the runtime trouble caused by this killer poke in this post.

于 2017-05-18T15:09:35.930 回答
4

我认为这不是错误。而是 C# 编译器的计算方式%(这是一个猜测)。似乎 C# 编译器首先计算%正数,然后应用符号。如果Abs(long.MinValue + 1) == Abs(long.MaxValue)我们写:

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

现在我们将看到0正确的答案,因为现在Abs(dividend) == Abs(long.MaxValue)在范围内。

为什么当我们将它声明为一个const值时它会起作用呢?(再次猜测)似乎 C# 编译器实际上在编译时计算表达式并且不考虑常量的类型并将其作为一个BigInteger或某事(错误?)。因为如果我们声明一个像这样的函数:

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

并调用Console.WriteLine(Compute(dividend, divisor));我们将得到相同的异常。同样,如果我们这样声明常量:

const long dividend = long.MinValue + 1;

我们不会得到例外。

于 2013-08-18T16:47:50.987 回答