Целочисленное переполнение без знака хорошо определяется стандартами C и C ++. Например, стандарт C99 ( §6.2.5/9
) гласит
Вычисления с использованием беззнаковых операндов никогда не могут переполниться, потому что результат, который не может быть представлен результирующим целочисленным типом без знака, уменьшается по модулю на число, которое на единицу больше наибольшего значения, которое может быть представлено результирующим типом.
Однако оба стандарта утверждают, что целочисленное переполнение со знаком является неопределенным поведением. Опять же из стандарта C99 ( §3.4.3/1
)
Примером неопределенного поведения является поведение на целочисленном потоке
Есть ли историческая или (еще лучше!) Техническая причина этого несоответствия?
c++
c
undefined-behavior
integer-overflow
Энтони Валле-Дюбуа
источник
источник
if (a + b < a)
). Переполнение при умножении сложно для типов со знаком и без знака.MAX_INT+1 == -0
, в то время как на дополнении до двух это будетINT_MIN
Ответы:
Историческая причина в том, что большинство реализаций C (компиляторов) просто использовали любое поведение переполнения, которое было проще всего реализовать с целочисленным представлением, которое оно использовало. Реализации C обычно используют то же представление, которое использует ЦП, поэтому поведение переполнения следует из целочисленного представления, используемого ЦП.
На практике только представления для подписанных значений могут различаться в зависимости от реализации: одно дополнение, два дополнения, знак-величина. Для беззнакового типа в стандарте нет оснований разрешать изменение, поскольку существует только одно явное двоичное представление (стандарт допускает только двоичное представление).
Соответствующие цитаты:
C99 6.2.6.1:3 :
C99 6.2.6.2:2 :
В настоящее время все процессоры используют представление дополнения до двух, но знаковое арифметическое переполнение остается неопределенным, и производители компиляторов хотят, чтобы оно оставалось неопределенным, потому что они используют эту неопределенность, чтобы помочь с оптимизацией. См., Например, этот пост в блоге Иана Ланса Тейлора или эту жалобу Агнера Фога и ответы на его сообщение об ошибке.
источник
Помимо хорошего ответа Паскаля (который, я уверен, является основной мотивацией), также возможно, что некоторые процессоры вызывают исключение при целочисленном переполнении со знаком, что, конечно, могло бы вызвать проблемы, если компилятору пришлось «организовать другое поведение» ( например, используйте дополнительные инструкции для проверки на возможное переполнение и рассчитайте иначе в этом случае).
Стоит также отметить, что «неопределенное поведение» не означает «не работает». Это означает, что реализация может делать все, что угодно в этой ситуации. Это включает в себя выполнение «правильных действий», а также «вызов полиции» или «сбой». Большинство компиляторов, когда это возможно, выбирают «поступать правильно», предполагая, что это относительно легко определить (в данном случае это так). Однако, если у вас возникают переполнения в вычислениях, важно понимать, к чему это приводит, и что компилятор МОЖЕТ делать что-то другое, чем вы ожидаете (и это может очень зависеть от версии компилятора, настроек оптимизации и т. Д.) ,
источник
int f(int x) { return x+1>x; }
с оптимизацией. GCC и ICC, с опциями по умолчанию, оптимизируют вышеуказанное доreturn 1;
.int
переполнении в зависимости от уровней оптимизации, см. Ideone.com/cki8nM. Думаю, это демонстрирует, что ваш ответ дает плохой совет.Прежде всего, обратите внимание, что C11 3.4.3, как и все примеры и примечания, не является нормативным текстом и поэтому не имеет отношения к цитированию!
Соответствующий текст, в котором говорится, что переполнение целых чисел и чисел с плавающей точкой является неопределенным поведением:
С11 6,5 / 5
Разъяснения относительно поведения целочисленных типов без знака можно найти здесь:
С11 6.2.5 / 9
Это делает целочисленные типы без знака особым случаем.
Также обратите внимание, что есть исключение, если какой-либо тип преобразуется в тип со знаком и старое значение больше не может быть представлено. Поведение тогда просто определяется реализацией, хотя сигнал может быть повышен.
С11 6.3.1.3
источник
В дополнение к другим упомянутым проблемам, использование математического переноса без знака приводит к тому, что целочисленные типы без знака ведут себя как абстрактные алгебраические группы (это означает, что, среди прочего, для любой пары значений
X
иY
будет существовать некоторое другое значение,Z
такое,X+Z
которое при правильном приведении равноY
иY-Z
будет, если правильно разыгран, равноX
). Если беззнаковые значения были просто типами места хранения, а не типами промежуточных выражений (например, если не было беззнакового эквивалента самого большого целочисленного типа, и арифметические операции над беззнаковыми типами вели себя так, как если бы они были сначала преобразованы в более крупные знаковые типы, тогда не было бы такой большой потребности в определенном поведении обтекания, но трудно делать вычисления в типе, который не имеет, например, аддитивного обратного.Это помогает в ситуациях, когда поведение с циклическим изменением действительно полезно - например, с порядковыми номерами TCP или некоторыми алгоритмами, такими как вычисление хеша. Это также может помочь в ситуациях, когда необходимо обнаружить переполнение, поскольку выполнение вычислений и проверка их переполнения зачастую проще, чем предварительная проверка их переполнения, особенно если в расчетах используется наибольший доступный целочисленный тип.
источник
a+b-c
вычисляется в цикле, ноb
иc
являются постоянными в пределах этого цикла, это может быть полезно , чтобы переместить вычисление(b-c)
вне цикла, но делает это потребует среди других вещей , которые(b-c)
дают значение , которое, при добавлении кa
, дастa+b-c
, что, в свою очередь, требует, чтобыc
иметь аддитивную обратную.(a+b)-c
соответствуетa+(b-c)
представимости арифметического значенияb-c
в типе, подстановка будет действительной независимо от возможного диапазона значений для(b-c)
.Возможно, еще одна причина, по которой определяется арифметика без знака, состоит в том, что числа без знака образуют целые числа по модулю 2 ^ n, где n - ширина числа без знака. Беззнаковые числа - это просто целые числа, представленные двоичными цифрами вместо десятичных. Выполнение стандартных операций в модульной системе хорошо понятно.
Цитата ОП ссылается на этот факт, но также подчеркивает тот факт, что существует только один, однозначный, логический способ представления целых чисел без знака в двоичном виде. Напротив, числа со знаком чаще всего представлены с использованием дополнения до двух, но возможны другие варианты, как описано в стандарте (раздел 6.2.6.2).
Представление дополнения Two позволяет определенным операциям иметь больше смысла в двоичном формате. Например, увеличение отрицательных чисел такое же, как и для положительных чисел (ожидайте в условиях переполнения). Некоторые операции на уровне машины могут быть одинаковыми для чисел со знаком и без знака. Однако при интерпретации результата этих операций некоторые случаи не имеют смысла - положительное и отрицательное переполнение. Кроме того, результаты переполнения различаются в зависимости от базового подписанного представления.
источник