为什么编译器会评估与运行时不同的余数MinValue%-1?

时间:2013-08-18 12:25:29

标签: c# operators modulus compiler-bug overflowexception

我认为这看起来像是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#语言规范专门提到了这个案例,并说要抛出System.OverflowException。它不依赖于上下文checkedunchecked(对于余数运算符的编译时常量操作数的错误与checkedunchecked相同)。

intSystem.Int32)发生同样的错误,而不只是longSystem.Int64)。

为了进行比较,编译器处理dividend / divisor const个操作数比dividend % divisor好得多。

我的问题:

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

2 个答案:

答案 0 :(得分:19)

这个角落案例在编译器中得到了非常具体的解决。 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);

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

也是传统C ++版本编译器的行为,一直回到版本1.从SSCLI v1.0发行版,clr / src / 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;

所以得出的结论是,这一点并没有被忽视或遗忘,至少对于编译器工作的程序员来说,它可能被认为是C#语言规范中不够精确的语言。更多关于this post中这个杀手捅引起的运行时问题。

答案 1 :(得分: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或其他东西(bug?)进行操作。因为如果我们声明一个像:

这样的函数
static long Compute(long l1, long l2)
{
    return l1 % l2;
}

并致电Console.WriteLine(Compute(dividend, divisor));我们会得到同样的例外。再说一遍,如果我们像这样宣布常数:

const long dividend = long.MinValue + 1;

我们不会得到例外。