Целочисленное переполнение в стандартах и ​​компиляторах C:

Отредактировано, чтобы включить надлежащую стандартную ссылку благодаря Carl Norum.

Стандартные состояния C

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

Существуют ли ключи компилятора, которые гарантируют определенное поведение при переполнении целого числа? Я бы хотел избежать носовых демонов. В частности, я хотел бы заставить компилятор обернуться при переполнении.

Для единственности возьмем стандарт, чтобы быть C99, а компилятор - gcc. Но меня бы интересовали ответы других компиляторов (icc, cl) и других стандартов (C1x, C89). На самом деле, просто чтобы раздражать толпу C/С++, я бы даже оценил ответы на С++ 0x, С++ 03 и С++ 98.

Примечание: Международный стандарт ISO/IEC 10967-1 может иметь значение здесь, но, насколько я мог судить, он упоминался только в информационном приложении.

Ответ 1

Посмотрите -ftrapv и -fwrapv:

-ftrapv

Эта опция генерирует ловушки для подписанного переполнения для операций сложения, вычитания, умножения.

-fwrapv

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

Ответ 2

Для вашего ответа C99, я думаю, 6.5 выражений, пункт 5 - это то, что вы ищете:

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

Это означает, что если вы получаете переполнение, вам не повезло - никакого поведения не гарантировано. Неподписанные типы являются особым случаем и никогда не переполняются ( 6.2.5 Типы, пункт 9):

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

С++ имеет те же самые утверждения, которые написаны несколько иначе:

  • 5 выражений, пункт 4:

    Если во время оценки выражения результат не определяется математически или нет в диапазоне отображаемых значений для его типа, поведение undefined. [Примечание: большинство существующих реализаций С++ игнорируют целые переполнения. Обработка деления на ноль, формирование остатка с использованием делителя нуля, и все исключения с плавающей запятой различаются между машинами и обычно регулируются библиотечной функцией. -endnote]

  • 3.9.1 Основные типы, пункт 4:

    Незнакомые целые числа, объявленные unsigned, должны подчиняться законам арифметики по модулю 2 ^ n, где n - количество бит в представлении значений этого конкретного размера целого числа.

Ответ 3

В C99 общее поведение описано в 6.5/5

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

Поведение неподписанных типов описано в 6.2.5/9, в котором в основном говорится, что операции с неподписанными типами никогда не приводят к исключительному условию

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

Компилятор GCC имеет специальную опцию -ftrapv, которая предназначена для захвата переполнения во время выполнения целых операций со знаком.

Ответ 4

Для полноты я хотел бы добавить, что Clang теперь имеет "проверенные арифметические встроенные функции" в качестве расширения языка. Вот пример использования проверенного беззнакового умножения:

unsigned x, y, result;
...
if (__builtin_umul_overflow(x, y, &result)) {
    /* overflow occured */
    ...
}
...

http://clang.llvm.org/docs/LanguageExtensions.html#checked-arithmetic-builtins

Ответ 5

6.2.5 пункт 9 - это то, что вы ищете:

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

Ответ 6

Я не уверен, есть ли какие-либо переключатели компилятора, которые вы можете использовать для обеспечения единообразного поведения для переполнения в C/С++. Другой вариант - использовать шаблон SafeInt<T>. Это кроссплатформенный С++-шаблон, который обеспечивает окончательные проверки переполнения/недогрузки для всех типов целых операций.

Ответ 7

Все предыдущие публикации комментировали стандарт C99, но на самом деле эта гарантия уже была доступна ранее.

Пятый абзац раздела 6.1.2.5 Типы

стандартных состояний C89

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

Обратите внимание, что это позволяет программистам C заменять все беззнаковые деления на некоторую константу, которая заменяется умножением на обратный элемент кольца, образованный C по модулю 2 ^ N интервальной арифметики.

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

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

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

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