Нет полезного и надежного способа обнаружения целочисленного переполнения в C/С++?

Возможный дубликат:
Лучший способ обнаружения переполнения целых чисел в C/С++

Нет, это не дубликат. Проблема та же, но вопрос другой.

Компилятор gcc может оптимизировать проверку переполнения (с -O2), например:

int a, b;
b = abs(a);                     // will overflow if a = 0x80000000
if (b < 0) printf("overflow");  // optimized away

Люди gcc утверждают, что это не ошибка. Overflow - это поведение undefined, в соответствии со стандартом C, которое позволяет компилятору что-либо делать. Видимо, что-то подразумевает, что переполнение никогда не происходит. К сожалению, это позволяет компилятору оптимизировать проверку переполнения.

Безопасный способ проверки переполнения описан в недавней документе CERT. В этой статье рекомендуется сделать что-то подобное перед добавлением двух целых чисел:

if ( ((si1^si2) | (((si1^(~(si1^si2) & INT_MIN)) + si2)^si2)) >= 0) { 
  /* handle error condition */
} else {
  sum = si1 + si2;
}

По-видимому, вам нужно сделать что-то вроде этого перед каждыми +, -, *,/и другими операциями в серии вычислений, когда вы хотите убедиться, что результат действителен. Например, если вы хотите убедиться, что индекс массива не выходит за рамки. Это так громоздко, что практически никто этого не делает. По крайней мере, я никогда не видел программу C/С++, которая делает это систематически.

Теперь это фундаментальная проблема:

  • Проверка индекса массива перед доступом к массиву полезна, но не надежна.

  • Проверка каждой операции в серии вычислений с помощью метода CERT является надежной, но не полезной.

  • Заключение: нет полезного и надежного способа проверки переполнения в C/С++!

Я отказываюсь верить, что это было предназначено, когда был написан стандарт.

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

Теперь мой вопрос: Являются ли люди gcc интерпретацией "undefined поведения" слишком далеко, когда он позволяет им оптимизировать проверку переполнения или нарушен стандарт C/С++?

Добавлено примечание: Извините, вы, возможно, неправильно поняли мой вопрос. Я не спрашиваю, как обойти проблему - на это уже ответил в другом месте. Я задаю более фундаментальный вопрос о стандарте C. Если нет полезного и надежного способа проверки переполнения, то сам язык сомнительный. Например, если я создаю класс безопасного массива с проверкой границ, тогда я должен быть в безопасности, но я не уверен, что проверка границ может быть оптимизирована.

Если стандарт позволяет это выполнить, то либо стандартная необходимость пересмотра, либо интерпретация стандарта нуждается в пересмотре.

Добавлено примечание 2: Люди здесь, похоже, не хотят обсуждать сомнительную концепцию поведения undefined. Тот факт, что в стандарте C99 перечислены 191 различные виды поведения undefined (ссылка), является показателем неаккуратного стандарта.

Многие программисты с готовностью принимают утверждение о том, что "undefined поведение" дает лицензии делать что-либо, включая форматирование вашего жесткого диска. Я думаю, что проблема заключается в том, что стандарт переводит целочисленное переполнение в ту же опасную категорию, что и запись границ внешнего массива.

Почему эти два типа поведения "w91 > " отличаются? Потому что:

  • Многие программы полагаются на целочисленное переполнение, являющееся доброкачественным, но мало программ полагаются на запись границ внешнего массива, когда вы не знаете, что там есть.

  • Написание внешних границ массива на самом деле может сделать что-то плохое, как форматирование вашего жесткого диска (по крайней мере, в незащищенной ОС, такой как DOS), и большинство программистов знают, что это опасно.

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

  • Ошибка, такая как запись границ внешнего массива, может быть найдена с помощью отладчика, но ошибка оптимизации проверки переполнения невозможна, поскольку при отладке обычно отключается оптимизация.

  • Компилятор gcc явно воздерживается от политики "ничего идет" в случае переполнения целых чисел. Есть много случаев, когда он воздерживается от оптимизации, например. цикл, если он не может проверить, что переполнение невозможно. По какой-то причине люди gcc признали, что у нас будет слишком много ошибок, если они будут следовать политике "все идет", но они имеют другое отношение к проблеме оптимизации проверки переполнения.

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

Ответ 1

Задайте себе вопрос: как часто вам нужна проверенная арифметика? Если вам это нужно, вы должны написать класс checked_int, который перегружает общие операторы и инкапсулирует проверки в этот класс. Реквизиты для совместного использования на веб-сайте с открытым исходным кодом.

Еще лучше (возможно), используйте класс big_integer, чтобы переполнение не могло произойти в первую очередь.

Ответ 2

Разработчики gcc здесь совершенно верны. Когда стандарт говорит, что поведение undefined означает, что в компиляторе есть требования нет.

Как действительная программа не может делать ничего, что вызывает UB (так как тогда это уже недействительно), компилятор вполне может предположить, что UB не произойдет. И если это все равно, все, что делает компилятор, будет в порядке.

Для вашей проблемы с переполнением одним из решений является рассмотрение того, какие диапазоны должны обрабатывать caclulations. Например, при балансировании моего банковского счета я могу предположить, что суммы будут значительно ниже 1 миллиарда, так что 32-битный int будет работать.

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

Ответ 3

int a, b;
b = abs(a); // will overflow if a = 0x80000000
if (b < 0) printf("overflow");  // optimized away 

(Кажется, вы предполагаете дополнение 2s... пусть это будет работать)

Кто говорит, что abs(a) "переполняет", если a имеет этот двоичный шаблон (точнее, если a есть INT_MIN)? Страница руководства Linux для abs(int) говорит:

Попытка взять абсолютное значение самого отрицательного целого не определена.

Не определено не обязательно означает переполнение.

Итак, ваша предпосылка, что b может быть меньше 0, и что как-то тест на "переполнение", в корне ошибочен с самого начала. Если вы хотите протестировать, вы не можете сделать это в результате, который может иметь поведение undefined - сделайте это перед операцией!

Если вам это интересно, вы можете использовать пользовательские типы С++ (например, классы) для реализации собственного набора тестов вокруг необходимых операций (или найти библиотеку, которая уже делает это). Языку не нужна встроенная поддержка для этого, так как он может быть реализован одинаково эффективно в такой библиотеке, при этом семантика использования не изменяется. Эта фундаментальная сила - одна из великих вещей в С++.

Ответ 4

Просто используйте правильный тип для b:

int a;
unsigned b = a;
if (b == (unsigned)INT_MIN) printf("overflow");  // never optimized away
else b = abs(a);

Изменить: Тест для переполнения в C можно безопасно выполнить с помощью неподписанного типа. Беззнаковые типы, просто обернутые вокруг арифметических и подписанных типов, безопасно преобразуются в них. Поэтому вы можете сделать любой тест на них, который вам нравится. На современных процессорах это преобразование обычно является просто переинтерпретацией регистра или так, поэтому оно не требует затрат времени исполнения.