C# 为什么编译器计算的值与运行时的值%1不同?

C# 为什么编译器计算的值与运行时的值%1不同?,c#,operators,modulus,compiler-bug,overflowexception,C#,Operators,Modulus,Compiler Bug,Overflowexception,我认为这看起来像是C#编译器中的一个bug 考虑以下代码(在方法内部): 它编译时没有错误(或警告)看起来像个bug。运行时,在控制台上打印0 如果没有常量,则代码: long dividend = long.MinValue; long divisor = -1L; Console.WriteLine(dividend % divisor); 当运行此命令时,它会正确地导致抛出OverflowException C语言规范特别提到了这种情况,并说应该抛出一个系统。OverflowExcept

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

考虑以下代码(在方法内部):

它编译时没有错误(或警告)看起来像个bug。运行时,在控制台上打印
0

如果没有
常量
,则代码:

long dividend = long.MinValue;
long divisor = -1L;
Console.WriteLine(dividend % divisor);
当运行此命令时,它会正确地导致抛出
OverflowException

C语言规范特别提到了这种情况,并说应该抛出一个
系统。OverflowException
。它似乎不依赖于上下文
选中的
未选中的
(编译时常量操作数到余数运算符的错误与
选中的
未选中的
相同)

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

作为比较,编译器使用
const
操作数处理
被除数/除数
被除数%除数
好得多

我的问题是:


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

我认为这不是一个bug;而是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(股息)==Abs(long.MaxValue)
在范围内

那么,当我们将它声明为
const
值时,它为什么会起作用呢?(再一次猜测)C#编译器似乎在编译时实际计算表达式,而不考虑常量的类型,并将其作为
BigInteger
或其他什么(bug?)来处理。因为如果我们声明一个函数,比如:

static long Compute(long l1, long l2)
{
    return l1 % l2;
}
并调用
Console.WriteLine(计算(被除数,除数))我们将得到相同的异常。同样,如果我们这样声明常数:

const long dividend = long.MinValue + 1;

我们不会得到异常。

编译器中特别处理了这种情况。中最相关的注释和代码:

以及:

也是编译器的传统C++版本的行为,一直追溯到版本1。从sscliv1.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语言规范中,它可能被限定为不够精确的语言。更多关于这个杀手插嘴造成的运行时问题。

提到@EricLippert可能会吸引到合适的人群来回答这个问题:)@Morten,在这一点上,他可能只是从Coverity的栖木上困惑地凝视着。)我想你应该悬赏,因为这让我很恼火,为什么会发生这种事。规范指出,任何可能引发运行时异常的常量表达式在编译时都会导致编译时错误@嗅探:它还没有开放接受赏金。一些权威的回答者仍有可能出现。但如果不是,欢迎任何人设置一个悬赏:-)我不认为当除以负数时,你可以模化并有余数,因为它向零舍入。(取决于芯片,可能基于与对等方的通信)。负因子,oi。好奇这是否有帮助!这些我都知道了。请注意,规范中说:
x%y
的结果是由
x–(x/y)*y
产生的值。如果
y
为零,则抛出
系统.dividebyzero异常。↵↵ 如果左操作数是最小的
int
long
值,右操作数是
-1
,则抛出
系统溢出异常。[…]从您(和我)的观察中可以明显看出,当余数在编译时计算时,编译器没有遵循规范。运行时确实符合规范。我很抱歉;我没有读说明书。是的;我现在在我的回答中也看到了“作为一个大整数或什么(bug?)来处理它”。你说得对。
// 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;
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;