Почему деление int.MinValue на -1 бросило OverflowException в неконтролируемый контекст?

int y = -2147483648;
int z = unchecked(y / -1);

Вторая строка вызывает OverflowException. Не следует ли unchecked предотвратить это?

Например:

int y = -2147483648;
int z = unchecked(y * 2);

не вызывает исключения.

Ответ 1

Это не исключение, что компилятор С# или джиттер имеют какой-либо контроль. Он специфичен для процессоров Intel/AMD, при создании команды IDIV CPU генерирует ловушку #DE (Divide Error). Операционная система обрабатывает ловушку процессора и отражает ее обратно в процесс с исключением STATUS_INTEGER_OVERFLOW. CLR покорно переводит его в соответствующее управляемое исключение.

Руководство Intel Processor Manual - это не просто золотая жила информации об этом:

Нецелые результаты усекаются (нарезаются) на 0. Остальная часть всегда меньше делителя по величине. Переполнение указана с исключением #DE (ошибка деления), а не с флагом CF.

По-английски: результат подписанного деления - +2147483648, не представляемый в int, так как это Int32.MaxValue + 1. В противном случае неизбежный побочный эффект того, как процессор представляет отрицательные значения, он использует два дополнения кодирование. Который создает единственное значение для представления 0, оставляя нечетное число других возможных кодировок для представления отрицательных и положительных значений. Для отрицательных значений есть еще один. Тот же тип переполнения, что и -Int32.MinValue, за исключением того, что процессор не ловутся на инструкцию NEG и просто создает результат мусора.

Язык С#, конечно, не единственный с этой проблемой. Спецификация языка С# делает это поведение, определенное реализацией (глава 7.8.2), отмечая особое поведение. Никакой другой разумной вещи, которую они могли бы с этим сделать, генерация кода для обработки исключения, безусловно, считалась слишком непрактичной, создавая неигромо медленный код. Не путь С#.

Язык C и С++ специфицирует анте, создавая поведение undefined. Это действительно может стать уродливым, как программа, скомпилированная с компилятором gcc или g++, как правило, с инструментальной комбинацией MinGW. Который имеет несовершенную поддержку времени исполнения для SEH, он проглатывает исключение и позволяет процессору перезапустить инструкцию деления. Программа зависает, сжигая 100% ядро, при этом процессор постоянно генерирует ловушки #DE. Превращение деления в легендарную команду Halt and Catch Fire:)

Ответ 2

Раздел 7.72 (Оператор отдела) спецификаций С# 4 гласит:

Если левый операнд является наименьшим представимым значением int или long, а правый операнд равен -1, происходит переполнение. В проверенном контексте [...]. В неконтролируемом контексте определяется реализацией того, выбрано ли исключение System.ArithmeticException(или его подкласс), или переполнение не отображается, а результирующее значение имеет значение, равное левому операнду. p >

Таким образом, тот факт, что это исключает исключение в неконтролируемом контексте, на самом деле не является ошибкой, поскольку поведение определяется реализацией.

Ответ 3

В соответствии с разделом 7.8.2 С# Language Specification 5.0 мы имеем следующий случай:

7.8.2 Оператор отдела
Для операции вида x/y бинарная перегрузка оператора разрешение (§7.3.4) применяется для выбора конкретного оператора реализация. Операнды преобразуются в типы параметров выбранный оператор, а тип результата - тип возврата оператора. Ниже перечислены предопределенные операторы деления. Операторы вычисляют отношение x и y.

  • Целочисленное деление:
    int operator /(int x, int y);
    uint operator /(uint x, uint y);
    long operator /(long x, long y);
    ulong operator /(ulong x, ulong y);
    Если значение правого операнда равно нулю, бросается a System.DivideByZeroException. Деление округляет результат к нулю. Таким образом, абсолютное значение результата представляет собой наибольшее возможное целое число, которое меньше или равно абсолютному значению частного из двух операндов. Результат равен нулю или положителен, если два операнда имеют один и тот же знак и ноль или отрицательный, когда два операнда имеют противоположные знаки. Если левый операнд является наименьшим представляемым значением int или long, а правый операнд -1, происходит переполнение. В проверенном контексте это вызывает отклонение System.ArithmeticException (или его подкласса). В неконтролируемом контексте он определяется реализацией относительно того, был ли выведен System.ArithmeticException (или его подкласс) или переполнение не отображается, а результирующее значение - в левом операнде.