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

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

Вторая строка вызывает ошибку OverflowException. Разве unchecked не должен этому помешать?

Например:

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

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


person Marcel Niehüsener    schedule 27.10.2014    source источник
comment
похоже на ошибку. в какой версии .net вы используете свой код?   -  person Vinicius Kamakura    schedule 27.10.2014
comment
вы не можете использовать длинный?   -  person DJ Burb    schedule 27.10.2014
comment
@DJBurb Вопрос не в том, как это обойти, а в том, чтобы объяснить поведение. Код не должен генерировать исключение.   -  person Servy    schedule 27.10.2014
comment
@hexa также без флажка, он не будет брошен.   -  person Sriram Sakthivel    schedule 27.10.2014
comment
@hexa Это не двойное переполнение, а только целое число.   -  person Servy    schedule 27.10.2014
comment
@Servy Даже если это переполняется дважды, это не вызовет исключения. переполнение с плавающей запятой не приведет к исключению IIRC.   -  person Sriram Sakthivel    schedule 27.10.2014
comment
Единственный возможный способ переполнения int при выполнении деления — это точно два перечисленных здесь операнда, а именно деление int.MinValue на -1. Каждая другая пара операндов не переполняется, поэтому они, вероятно, просто не подумали об этом случае и неправильно предположили, что целочисленное деление никогда не переполнится.   -  person Servy    schedule 27.10.2014
comment
вы читали документацию.. просто любопытно непроверенная ссылка MSDN   -  person MethodMan    schedule 27.10.2014
comment
@DJKRAZE эта ссылка на испанском: P   -  person eddie_cat    schedule 27.10.2014
comment
если вы используете Google, появляется возможность перевода. Я опубликую ссылку Непроверенная ссылка на английскую версию MSDN   -  person MethodMan    schedule 27.10.2014
comment
Хотя предполагается, что unchecked позволяет избежать исключений переполнения, C# должен был бы генерировать код вокруг каждого непроверенного деления, чтобы проверить этот конкретный случай перед вызовом idiv — это замедлило бы деление в непроверенных контекстах, что было бы довольно неинтуитивно, поскольку unchecked должен < i>удалить дополнительные проверки и улучшить производительность..   -  person Blorgbeard    schedule 27.10.2014
comment
@HansPassant Я понимаю, что вы удалили свой ответ, хотя это и прискорбно. (Возможно, здесь что-то не так с системой...)   -  person ispiro    schedule 27.10.2014
comment
За него сильно проголосовали. Толпа C# не очень интересуется тем, как работают процессоры.   -  person Hans Passant    schedule 27.10.2014
comment
@HansPassant - тогда они не настоящие разработчики!   -  person Ahmed ilyas    schedule 27.10.2014
comment
@HansPassant Я согласен с мнением, но ваш ответ также был отвлекающим маневром в отношении реальной причины поведения в посте OP.   -  person Michael    schedule 27.10.2014
comment
@Майкл, я не согласен. Ответ Ханса, вероятно, является причиной того, что спецификации (в текущем единственном ответе) объясняют, почему это так.   -  person ispiro    schedule 27.10.2014
comment
Точно. Они не могли решить эту проблему, не делая деления очень дорогими, поэтому им пришлось задокументировать особый случай.   -  person Hans Passant    schedule 27.10.2014
comment
@HansPassant: У вас больше голосов за, чем против, я думаю, вам следует вернуть свой ответ. Тем более, что это своего рода вторая/другая половина ответа Серви.   -  person Neolisk    schedule 27.10.2014
comment
Мех, я буду держать это в заднем кармане, пока кто-нибудь не спросит, почему существует специальное правило.   -  person Hans Passant    schedule 27.10.2014
comment
@HansPassant, если ваш ответ был связан с принятым ответом, это кажется странным отношением со стороны кого-то, кого я уверен, что он больше всего считает авторитетом в этом вопросе.   -  person Michael    schedule 27.10.2014
comment
@Blorgbeard: Division по своей природе достаточно медленная, поэтому добавление небольшого количества условной логики не должно сделать ее хуже. На самом деле, я очень удивлен, что .NET на самом деле выполняет деление на константу -1 с помощью инструкции деления, а не использует какой-то другой более быстрый механизм, который (в непроверенном контексте) не будет заботиться о переполнении.   -  person supercat    schedule 27.10.2014
comment
@supercat Я сомневаюсь, что это того стоит. Почему C # добавляет эти посторонние CMP и JNE в мой узкий цикл, содержащий инструкцию деления, только когда он не отмечен? Разве unchecked не должен быть быстрее?..   -  person Blorgbeard    schedule 27.10.2014
comment
@Blorgbeard: если делитель является константой, инструкция деления, вероятно, может быть заменена другими более быстрыми инструкциями (я предполагаю, что для большинства процессоров будет все возможное постоянное делимое на последовательность инструкций без DIV, которая будет быстрее, чем IDIV). Даже для непостоянных делителей некоторая условная логика может улучшить производительность во многих типичных случаях, особенно если ветвления были правильно предсказаны.   -  person supercat    schedule 27.10.2014
comment
В теге unchecked не упоминается C#, но вместо этого упоминается javac - возможно, было бы неплохо обновить отрывок из тега или убрать его из этого вопроса (я не могу этого сделать, поскольку никогда не использовал ключевое слово unchecked). Точно так же checked относится к HTML...   -  person Thomas    schedule 28.10.2014
comment
Связано: Как остановить исключение OverflowException при целочисленном делении?   -  person Jeppe Stig Nielsen    schedule 28.10.2014


Ответы (3)


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

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

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

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

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

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

person Hans Passant    schedule 27.10.2014
comment
Но язык C# не должен был генерировать код, который привел бы к такой ошибке в первую очередь (или он должен был проглотить исключение и обработать ситуацию, а не повторно генерировать), чтобы выполнить контракт unchecked. Тот факт, что C# повторно выдает ошибку, когда ее выдает процессор, на самом деле ничего не объясняет. - person Servy; 27.10.2014
comment
Для вашего редактирования весь смысл unchecked заключается в том, чтобы не генерировать исключения переполнения при переполнении целочисленной операции, а скорее в переносе. - person Servy; 27.10.2014
comment
Результат neg на -2147483648 не мусор, на самом деле это +2147483648 (при интерпретации без знака) - person harold; 29.10.2014
comment
Хмя, вот так ракету взрываешь софтом. - person Hans Passant; 29.10.2014

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

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

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

person Servy    schedule 27.10.2014
comment
Может быть, вы также можете процитировать ответ Ханса Пассана, в котором представлены подробности реализации, то есть, почему именно это не сработало для ОП? Утверждение, что, поскольку поведение не определено, мы видим какие-то странные вещи, вряд ли само по себе является ответом. - person Neolisk; 27.10.2014
comment
@Neolisk Но это так, в том-то и дело. Спецификации определяют, какое поведение вы можете ожидать от программы C#. То, что конкретная инструкция процессора вызывает ошибку при вызове с этими операндами, означает что-то только в том случае, если вы знаете, что этот код C# приводит к тому, что именно эта инструкция процессора вызывается как полная реализация этого кода C# (чего вы не знаете). Кто-то может запустить тот же код C# на компьютере, отличном от Intel, и он вернет int.MinValue. При написании этого кода на C# вам действительно нужно предполагать, что он может иметь любое поведение, вы даже не можете предположить, что он вызовет исключение. - person Servy; 27.10.2014
comment
Интересный. В старой версии .NET поведение было определенным: Если левый операнд является наименьшим представимым значением int или long, а правый операнд равен –1, происходит переполнение. В этой ситуации всегда выдается System.OverflowException, независимо от того, происходит ли операция в контексте checked или unchecked. - person Dmitry; 27.10.2014
comment
@Servy: я понимаю твою точку зрения. Я хочу сказать, что ОП запускал свою программу не на сферическом компьютере в вакууме. Это было конкретное аппаратное и программное обеспечение. Согласно ответу Ганса, такое поведение характерно для Intel/AMD, поэтому вы можете указать, почему возникла проблема OP, потому что они использовали C# на Intel/AMD. Да, может случиться так, что другие архитектуры обеспечивают аналогичное поведение, но лучше сделать предположение, чем оставлять его неопределенным. Если они не используют Intel/AMD, они могут прокомментировать ниже, а затем вы можете изменить свой ответ. - person Neolisk; 27.10.2014
comment
@Neolisk Очевидно, что ОП уже знает, какой из двух вариантов выбрал его конкретный ЦП, учитывая, что он заявил, что код выдает за него. Говорить, что ЦП, который он использует, в этой ситуации выбрасывает, а не возвращает первый операнд, кажется довольно бессмысленным. В любом случае, при использовании этого кода C# ему нужно писать код так, как будто поведение не определено, а не полагаться на какое-либо поведение, независимо от того, какой ЦП он использует. - person Servy; 27.10.2014
comment
@Servy: согласен - код не должен зависеть от архитектуры. Но вопрос касался исключения OverflowException, которое зависит от архитектуры. В духе StackOverflow идеальным ответом будет список всех существующих архитектур, в которых эта ошибка возникает как часть неопределенного поведения. Я понимаю, что это трудно собрать, поэтому было бы неплохо привести хотя бы один пример. В противном случае предположим, что компьютер ОП улетел в космос после деления на (-1), допустимо ли это? Ну, это вписывается в неопределенное поведение. Но вы, наверное, хотите уточнить, что компилятор C# здесь ни при чем. :) - person Neolisk; 27.10.2014
comment
Для хорошего ответа нужны оба. Во-первых, ему нужно процитировать спецификацию, чтобы выяснить, является ли это поведение преднамеренным или ошибкой. Затем необходимо описать поведение ЦП, чтобы оправдать это неинтуитивное дизайнерское решение. - person CodesInChaos; 27.10.2014
comment
@Neolisk Спецификации C# не говорят, что что-либо может произойти, включая взрыв вселенной, когда вы делаете этот вызов. В нем конкретно указано, что он либо возвращает первый операнд, либо выбрасывает. Вы можете полагаться на одно из этих двух событий, просто не знаете какое. Если я пишу этот код, я не вижу, как было бы полезно знать, какие процессоры будут иметь какое поведение. Вы должны написать код для поддержки обоих случаев, несмотря ни на что. Это не значит, что вы должны написать код только для поддержки одного, а затем сказать, что вы можете запустить эту программу только на машине Intel. - person Servy; 27.10.2014
comment
@CodesInChaos Фактическое поведение очевидно из результата. При выполнении кода выдается исключение, потому что инструкция ЦП выдает исключение. Утверждение, что это кажется очевидным. Вопрос заключается в том, почему здесь вызывается инструкция ЦП, которая выдает бросок, а не в том, выдает ли бросок инструкция ЦП или нет. Как вы думаете, кому на самом деле будет полезно прочитать код, потому что реализация этого поведения вызывает исключение? Это эффективно напрашивается вопрос. - person Servy; 27.10.2014
comment
@Servy Не очевидно, что исключение вызывает инструкция деления, а не явная проверка. Также не очевидно, есть ли альтернативная инструкция, которая не будет генерировать ошибки. Я подозреваю, что причина такого дизайнерского решения в том, что другое поведение привело бы к потере производительности на процессоре x86. Я предполагаю, что без снижения производительности дизайнеры предпочли бы не создавать исключения. Но я не уверен в этом, поскольку, как отмечает суперкат, деление в любом случае происходит медленно, и если вы поймаете исключение, вы понесете затраты только в редком случае min/-1. - person CodesInChaos; 27.10.2014
comment
@CodesInChaos Очевидно, что выдает инструкция. Если бы код C# добавлял проверку/бросок, тогда поведение не было бы неопределенным, оно, несомненно, вызывало бы исключение. Что касается того, почему команда C # решила использовать эту реализацию, я не собираюсь пытаться угадать, почему они решили сделать это и указать это в ответе. Производительность может быть причиной, а может и не быть, в любом случае я не в состоянии это комментировать; только кто-то из команды C # будет. - person Servy; 27.10.2014
comment
@Servy: Возможно, исторически .NET всегда использовала инструкцию деления (которая приводила к переполнению), поэтому она была определена таким образом, и через какое-то время JITter начал оптимизировать деление на определенные константы. Для совместимости обновленные версии JITter для старых фреймворков могут некоторое время включать проверку переполнения, но если никто не считает, что проверка переполнения в этом узком контексте служит какой-либо полезной цели, документирование поведения как неопределенного позволит исключить проверку в будущем. - person supercat; 27.10.2014
comment
Просто придирка к формулировкам. Спецификации говорят, что это определяется реализацией, но вы заключаете, что это не определено. Определенное реализацией означает, что существует определенное поведение, просто оно может варьироваться в зависимости от системы и т. д., в то время как неопределенное означает, что нет возможности рассуждать о его поведении. - person nhahtdh; 28.10.2014
comment
@nhahtdh: для меня это не противоречит сказанному выше, с точки зрения спецификации это не определено. - person BlueTrin; 28.10.2014
comment
Также обратите внимание на это: если вы измените объявление y на const, деление, конечно же, будет выполняться во время компиляции. И текущая реализация компилятора C# позволяет это (учитывая, что вы сохраняете ключевое слово unchecked явным); нет ошибки времени компиляции. Итак, у вас есть ситуация, когда удаление модификатора const изменит результат программы. Компилятор C# и среда выполнения не согласны с тем, как делить целые числа. - person Jeppe Stig Nielsen; 28.10.2014
comment
@nhahtdh: термин «неопределенное поведение», когда он используется в некоторых общих контекстах, означает, что что угодно может произойти, в то время как определение реализации означает, что разные реализации могут отличаться, но любая конкретная реализация будет согласованной. Ни одно из условий здесь не применимо. Лучшим термином может быть неопределенный или произвольный выбор среди определенных альтернатив, что означает, что цикл, который пытается вычислить -2147483648/-1, получить значение -2147483648 в первые 23 раза, а затем выдает ошибку исключение 24 числа. - person supercat; 24.06.2015

Согласно разделу 7.8.2 Спецификации языка C# 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);
    Если значение правого операнда равно нулю, выдается System.DivideByZeroException. При делении результат округляется до нуля. Таким образом, абсолютное значение результата — это максимально возможное целое число, меньшее или равное абсолютному значению частного двух операндов. Результат равен нулю или положителен, если два операнда имеют одинаковый знак, и равен нулю или отрицателен, если два операнда имеют противоположные знаки. Если левый операнд является наименьшим представимым целым или длинным значением, а правый операнд равен –1, возникает переполнение. В проверенном контексте это вызывает выброс System.ArithmeticException (или его подкласса). В непроверенном контексте реализация определяет, будет ли выброшен System.ArithmeticException (или его подкласс) или о переполнении не будет сообщено, а результирующее значение будет значением левого операнда.
person Markus Safar    schedule 27.10.2014
comment
Как так вышло, что вы процитировали другой раздел спецификации, но теми же словами? - person Neolisk; 27.10.2014
comment
@Neolisk Другая версия C#. Он цитирует 5.0, я цитирую 4.0. Очевидно, что соответствующий контент ничем не отличается. - person Servy; 27.10.2014