2013-08-18 7 views
22

Я думаю, что это похоже на ошибку в компиляторе C#.Почему компилятор оценивает остаток MinValue% -1, отличный от времени выполнения?

Рассмотрим этот код (внутри метода):

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. Это не зависит от контекста checked или unchecked (похоже, ошибка с постоянными операндами времени компиляции оператору останова совпадает с checked и unchecked).

же ошибка происходит с int (System.Int32), а не только long (System.Int64).

Для сравнения, компилятор обрабатывает dividend/divisor с const операндами намного лучше, чем dividend % divisor.

Мои вопросы:

Правильно ли я это ошибка? Если да, это известная ошибка, которую они не хотят исправлять (из-за обратной совместимости, даже если глупо использовать % -1 с постоянной времени компиляции -1)? Или мы должны сообщить об этом, чтобы они могли исправить его в следующих версиях компилятора C#?

+0

Упоминание @EricLippert могло бы привлечь нужную толпу для этого вопроса :) –

+0

@Morten, в этот момент он мог просто смотреть на него с окуня в Coverity. ;) –

+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/ЦСИ источник/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; 

Так вывод сделать, что это не было упущено или забыто о, по крайней мере, программистами, что ж orked на компиляторе, возможно, он может быть квалифицирован как недостаточно точный язык в спецификации языка C#. Подробнее о проблемах во время выполнения, вызванных этим убийцей, вытолкнуть в this post.

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; 

Мы не получили бы исключения.

+1

Я все это уже знал. Обратите внимание, что спецификация говорит: _ Результатом 'x% y' является значение, созданное' x - (x/y) * y'. Если 'y' равно нулю, генерируется' System.DivideByZeroException'. ↵↵ Если левый операнд является наименьшим значением 'int' или' long', а правый операнд '-1', генерируется' System.OverflowException'. [...] _ Из ваших наблюдений (и моих) видно, что компилятор не следует спецификации, когда остаток вычисляется во время компиляции. Среда выполнения соответствует спецификации. –

+0

Мои извинения; Я не читал спецификацию. Да; Я видел это сейчас в своем ответе тоже: «действует на него как BigInteger или что-то (ошибка?)». Ты прав. –