Почему вы хотите, чтобы произошло целочисленное переполнение?

В этом вопросе речь идет о том, как заставить VS проверять арифметическое переполнение в С# и выдавать исключение: C# Overflow не работает? Как включить проверку переполнения?

В одном из комментариев говорилось что-то странное, и за него много голосов, я надеюсь, что вы можете мне помочь:

Вы также можете использовать ключевое слово checked для переноса оператора или набора операторов, чтобы они явно проверялись на арифметическое переполнение. Установка свойства для всего проекта немного рискованна, потому что часто переполнение является вполне разумным ожиданием.

Я не очень разбираюсь в оборудовании, но знаю, что переполнение связано с тем, как работают регистры. Я всегда думал, что переполнение вызывает неопределенное поведение и его следует предотвращать, где это возможно. (в «обычных» проектах, без написания вредоносного кода)

Почему вы вообще ожидаете переполнения и почему бы вам не всегда предотвращать его, если у вас есть такая возможность? (путем установки соответствующей опции компилятора)


person magnattic    schedule 02.02.2011    source источник
comment
(ссылка) en.wikipedia.org/wiki/Integer_overflow   -  person Evan Mulawski    schedule 03.02.2011
comment
Переполнение вашего оператора вызывает неправильное поведение undefined... оно очень хорошо определено, особенно в случае целых чисел. Ваше заявление, что я мало знаю об оборудовании, говорит в этом случае... вам следует немного прочитать о двоичном коде и о том, как сложение работает на машинном уровне.   -  person JoelFan    schedule 03.02.2011
comment
Утверждение, вероятно, исходит из мышления C / C ++, переполнение - это неопределенное поведение, которое означает, что автор компилятора может делать вещи, которые вы не ожидаете при оптимизации. На самом деле не имеет значения, что ЦП имеет четко определенное поведение, автор компилятора с оптимизатором оценки константного выражения bignum может обнаружить это и удалить код, который не является программой C/C++, поэтому я могу сломать его. Я думаю, что никому не нужно заботиться о C #, потому что со временем новое оборудование делает вещи по-другому, MS перешел на какую-то новую серебряную пулю   -  person Rob11311    schedule 06.06.2014
comment
Переполнение — это очень хорошо определенное поведение в C и C++ для целых чисел без знака, но на самом деле поведение undefined для целых чисел со знаком.   -  person MicroVirus    schedule 16.06.2015


Ответы (13)


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

Мы проверили арифметику, включенную для всего проекта, для Noda Time. Я скорее выдам исключение, чем верну неверный результат. данные. Я подозреваю, что переполнение бывает желательным довольно редко... Я признаю, что обычно оставляю значение по умолчанию для непроверенной арифметики просто потому, что это значение по умолчанию. Есть, конечно, и штраф за скорость...

person Jon Skeet    schedule 02.02.2011
comment
Ваша точность и скорость действительно раздражают. Поздравляю! - person Andrei Rînea; 03.02.2011

Я всегда думал, что переполнение вызывает неопределенное поведение и его следует предотвращать, где это возможно.

Вас также может смутить разница между переполнением буфера (переполнением) и числовым переполнением.

Переполнение буфера — это когда данные записываются за конец неуправляемого массива. Это может привести к неопределенному поведению, например к перезаписи адреса возврата в стеке введенными пользователем данными. Переполнение буфера сложно реализовать в управляемом коде.

Однако числовое переполнение четко определено. Например, если у вас есть 8-битный регистр, он может хранить только 2 ^ 8 значений (от 0 до 255, если без знака). Таким образом, если вы добавите 100+200, вы получите не 300, а 300 по модулю 256, что равно 44. С использованием знаковых типов история немного сложнее; битовый шаблон увеличивается аналогичным образом, но они интерпретируются как дополнение до двух, поэтому добавление двух положительных чисел может дать отрицательное число.

person Justin    schedule 02.02.2011
comment
+1 за это отличное объяснение. Я знал разницу между буфером и арифметическим переполнением, но все же думал, что это (по крайней мере, в теории) неопределенное поведение во всех случаях. - person magnattic; 03.02.2011
comment
Знаете ли вы, кем хорошо определяется переполнение? Это оборудование? Это платформа (.NET, Java и т. д.)? Представьте, что вы хотите писать целочисленные хэш-коды (созданные без учета переполнения) как из программы .NET на 32-битной машине, так и из программы Java на 64-битной машине. Предполагая, что все целые числа имеют одинаковый размер, сопоставимы ли хэш-коды? - person Marco Eckstein; 29.03.2011
comment
@Marco - В большинстве случаев целочисленное переполнение будет четко определено на каждом уровне. Например, C#: любые значащие биты старшего разряда за пределами диапазон типа результата отбрасывается, Java: Если целочисленное сложение переполняется, то результатом являются младшие биты математической суммы, представленные в некотором достаточно большом формате с дополнением до двух. - person Justin; 01.04.2011
comment
@Marco - хэш-коды из C # и Java были бы сопоставимы по смыслу, если бы вы знали, что все шаги расчета были одинаковыми. Я бы полагался на это только в том случае, если бы вы написали фактический код для их вычисления в обоих случаях. Вы получите тот же результат от сложения двух целых чисел как в Java, так и в C#, если будете использовать целые числа одинакового размера и типа знака. - person Justin; 01.04.2011

При выполнении вычислений с постоянно увеличивающимися счетчиками. Классический пример — Environment.TickCount:

int start = Environment.TickCount;
DoSomething();
int end = Environment.TickCount;
int executionTime = end - start;

Если бы это было проверено, программа имела бы шансы взорваться через 27 дней после загрузки Windows. Когда TickCount превышает значение int.MaxValue во время выполнения DoSomething. Еще одним примером является PerformanceCounter.

Эти типы вычислений дают точный результат, даже если присутствует переполнение. Вторичным примером является вид математики, который вы делаете для создания репрезентативного битового шаблона, вас на самом деле не интересует точный результат, а только воспроизводимый. Примерами этого являются контрольные суммы, хэши и случайные числа.

person Hans Passant    schedule 02.02.2011
comment
В этом случае исключение, возможно, лучше, чем недополнение - часто прерывание лучше, чем создание неверных данных, которые затем могут каскадно распространяться по системе. Зависит от ситуации, конечно. - person Jon Skeet; 03.02.2011
comment
если он не переполняется более одного раза. :) - person darron; 03.02.2011
comment
@darron - это актуально для измерения времени одна миллисекунда. Угол «переполнение более одного раза» составляет 27 дней. Или 54, в зависимости от. - person Hans Passant; 03.02.2011

Углы

Целые числа, которые переполняются, являются элегантными инструментами для измерения углов. У вас есть 0 == 0 градусов, 0xFFFFFFFF == 359,999.... градусов. Это очень удобно, потому что как 32-битные целые числа вы можете добавлять/вычитать углы (350 градусов плюс 20 градусов заканчиваются переполнением, возвращаясь к 10 градусам). Также вы можете решить рассматривать 32-битное целое число как со знаком (от -180 до 180 градусов) и без знака (от 0 до 360). 0xFFFFFFFF соответствует -179,999..., что соответствует 359,999..., что эквивалентно. Очень элегантно.

person Doug T.    schedule 03.02.2011

При создании хэш-кодов, скажем, из строки символов.

person The Scrum Meister    schedule 02.02.2011

почему бы вам всегда не предотвратить это, если у вас есть возможность?

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

person Mark Byers    schedule 02.02.2011
comment
Хорошо, я этого не знал, и это звучит разумно. Это все еще не объясняет, почему в комментарии говорится, что часто переполнение является довольно разумным ожиданием? Глядя на то, как часто за него проголосовали (+10 за 2 часа), кажется, он не единственный, кто придерживается такого мнения? - person magnattic; 03.02.2011
comment
Разница в основном в арифметике со знаком. Из-за того, как работают дополнительные числа 2, 8-битное 11111111 на самом деле является отрицательным 1 вместо sByte.MinValue -128 (как вы могли ожидать). Хотя это имеет смысл; добавление 1 к -1 должно привести к нулю, поэтому добавление 00000001 к 11111111 == 00000000. Технически это арифметическое переполнение; бит переноса 1 с левого конца байта не имеет места в выделенном байте, и поэтому теряется. Однако целые числа постоянно находятся между положительными и отрицательными, поэтому это должно быть ошибкой только тогда, когда вы этого хотите. - person KeithS; 03.02.2011

Вероятно, это связано как с историей, так и с техническими причинами. Целочисленное переполнение очень часто используется с хорошим эффектом алгоритмами, которые полагаются на поведение (в частности, алгоритмы хеширования).

Кроме того, большинство ЦП допускают переполнение, но при этом устанавливают бит переноса, что упрощает реализацию сложения слов, размер которых превышает естественный. Реализация проверенных операций в этом контексте означала бы добавление кода для создания исключения, если установлен флаг переноса. Не слишком большое требование, но авторы компилятора, вероятно, не хотели навязывать его людям без выбора.

Альтернативой может быть проверка по умолчанию, но предложение непроверенной опции. Почему это не так, вероятно, тоже восходит к истории.

person Marcelo Cantos    schedule 02.02.2011

Вы можете ожидать этого от чего-то, что измеряется для дельт. Некоторое сетевое оборудование поддерживает небольшие размеры счетчиков, и вы можете опрашивать значение, например количество переданных байтов. Если значение становится слишком большим, оно просто переполняется до нуля. Это по-прежнему дает вам что-то полезное, если вы часто измеряете его (байты в минуту, байты в час), и, поскольку счетчики обычно очищаются при разрыве соединения, не имеет значения, что они не совсем точны.

Как упомянул Джастин, переполнение буфера — это совсем другое дело. Здесь вы записываете в память конец массива, чего не должны делать. При числовом переполнении используется такой же объем памяти. При переполнении буфера вы используете память, которую не выделяли. В некоторых языках переполнение буфера предотвращается автоматически.

person Pat L    schedule 02.02.2011

Есть одна классическая история о программисте, который воспользовался переполнением при разработке программы:

История Мела

person Jeff Dege    schedule 02.02.2011
comment
Насколько это полезный ответ? - person BoltClock; 03.02.2011
comment
Хотя это забавная история (возможно, городская легенда программиста), никто не должен делать ничего подобного в наши дни, если вы не работаете над прошивкой или чем-то сверхнизким уровнем. Определенно не в C# - person Davy8; 03.02.2011
comment
Должен быть комментарий, если что. - person ; 03.02.2011

Это связано не столько с тем, как работают регистры, сколько с ограничениями памяти в переменных, в которых хранятся данные. (Вы можете переполнить переменную в памяти без переполнения каких-либо регистров.)

Но чтобы ответить на ваш вопрос, рассмотрим простейший тип контрольной суммы. Это просто сумма всех проверяемых данных. Если контрольная сумма переполняется, это нормально, и часть, которая не переполнилась, по-прежнему имеет смысл.

Другие причины могут включать в себя то, что вы просто хотите, чтобы ваша программа продолжала работать, даже если несущественная переменная могла переполниться.

person Jonathan Wood    schedule 02.02.2011
comment
Процессоры не работают с памятью, они работают с регистрами. Регистр копируется в память после завершения работы над ним. Так что это действительно о том, как регистры переполняются. - person Bengie; 03.02.2011
comment
Откуда вы взяли информацию о том, что процессоры никогда не работают напрямую с памятью? Где переполнение регистра в операторе вроде ADD MyVar,1 или даже ADD [esi],1? Процессоры определенно работают непосредственно с памятью. - person Jonathan Wood; 30.10.2015

Еще одна возможная ситуация, которую я мог представить, - это алгоритм генерации случайных чисел - в этом случае мы не рассматриваем переполнение, потому что все, что нам нужно, это случайное число.

person EvgK    schedule 02.02.2011

Целочисленное переполнение происходит следующим образом.

У вас есть 8-битное целое число 1111 1111, теперь добавьте к нему 1. 0000 0000, ведущая 1 усекается, так как она будет на 9-й позиции.

Теперь скажем, что у вас есть целое число со знаком, старший бит означает, что оно отрицательное. Итак, теперь у вас есть 0111 1111. Добавьте к нему 1, и вы получите 1000 0000, то есть -128. В этом случае добавление 1 к 127 привело к переключению на отрицательное значение.

Я совершенно уверен, что переполнение ведет себя определенным образом, но я не уверен насчет недополнения.

person Bengie    schedule 02.02.2011

Вся целочисленная арифметика (ну хотя бы сложение, вычитание и умножение) является точной. Вам нужно быть осторожным только с интерпретацией полученных битов. В системе дополнения до 2 вы получаете правильный результат по модулю 2 на количество битов. Единственная разница между знаком и без знака заключается в том, что для чисел со знаком старший бит рассматривается как бит знака. Программист должен определить, что подходит. Очевидно, что для некоторых вычислений вы хотите знать о переполнении и предпринимать соответствующие действия, если оно обнаружено. Лично я никогда не нуждался в обнаружении переполнения. Я использую линейный конгруэнтный генератор случайных чисел, который опирается на него, то есть 64 * 64-битное целочисленное умножение без знака, меня интересуют только младшие 64 бита, я получаю операцию по модулю бесплатно из-за усечения.

person Omega Centauri    schedule 03.02.2011