Почему неподписанное целочисленное переполнение определенного поведения, но недопустимое целочисленное число целых чисел?

Целочисленное переполнение без знака хорошо определено как стандартами C, так и C ++. Например, стандарт C99 ( §6.2.5/9 )

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

Тем не менее, оба стандарта заявляют, что подписанное целочисленное переполнение является неопределенным поведением. Опять же, из стандарта C99 ( §3.4.3/1 )

Примером неустановленного поведения является поведение на целочисленном streamе

Есть ли историческая или (даже лучше!) Техническая причина этого несоответствия?

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

На практике только представления для подписанных значений могут различаться в зависимости от реализации: дополнения, дополнения двух, знак-величина. Для неподписанного типа нет причины, чтобы стандарт допускал вариацию, потому что существует только одно очевидное двоичное представление (стандарт допускает только двоичное представление).

Релевантные цитаты:

C99 6.2.6.1:3 :

Значения, хранящиеся в неподписанных битовых полях и объектах типа unsigned char, должны быть представлены с использованием чистой двоичной нотации.

C99 : 6.2.6.2 :

Если знаковый бит равен единице, значение должно быть изменено одним из следующих способов:

– отрицательное значение знака со знаком бит 0 ( знак и величина );

– бит знака имеет значение – (2 N ) ( дополнение двух );

– бит знака имеет значение – (2 N – 1) ( одно дополнение ).


В настоящее время все процессоры используют представление двух дополнений, но подписанное арифметическое переполнение остается неопределенным, и производители компиляторов хотят, чтобы он оставался неопределенным, потому что они используют эту неопределенность для оптимизации. См. Например, это сообщение в блоге Яна Лэнса Тейлора или эту жалобу Агнера Фога и ответы на его отчет об ошибке.

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

Также стоит отметить, что «неопределенное поведение» не означает, что «не работает». Это означает, что реализации разрешено делать то, что ему нравится в этой ситуации. Это включает в себя выполнение «правильной вещи», а также «вызов полиции» или «сбой». Большинство компиляторов, когда это возможно, будут выбирать «делать правильные вещи», считая, что относительно легко определить (в данном случае это так). Однако, если у вас возникают переполнения в вычислениях, важно понять, что это на самом деле приводит, и что компилятор МОЖЕТ делать что-то другое, кроме того, что вы ожидаете (и это может сильно зависеть от версии компилятора, настроек оптимизации и т. Д.), ,

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

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

Прежде всего, обратите внимание, что C11 3.4.3, как и все примеры и ноты, не является нормативным текстом и поэтому не имеет отношения к цитированию!

Соответствующий текст, в котором указано, что переполнение целых чисел и поплавков является неопределенным, заключается в следующем:

C11 6.5 / 5

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

Подробную информацию о поведении неподписанных целых типов можно найти здесь:

C11 6.2.5 / 9

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

Это делает неподписанные целые типы особым случаем.

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

C11 6.3.1.3

6.3.1.3 Целочисленные и беззнаковые целые числа

Когда значение с целым типом преобразуется в другой целочисленный тип, отличный от _Bool, если значение может быть представлено новым типом, оно не изменяется.

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

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

Возможно, еще одна причина того, почему unsigned арифметика определена, состоит в том, что беззнаковые числа образуют целые числа по модулю 2 ^ n, где n – ширина беззнакового числа. Беззнаковые числа представляют собой просто целые числа, представленные двоичными цифрами вместо десятичных цифр. Выполнение стандартных операций в системе модhive хорошо понято.

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

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

  • Как объяснить результат выражения (++ x) + (++ x) + (++ x)?
  • Итак, почему i = ++ i + 1 четко определен в C ++ 11?
  • Что такое строгое правило сглаживания?
  • Почему константные выражения имеют исключение для неопределенного поведения?
  • Почему целочисленное переполнение на x86 с GCC вызывает бесконечный цикл?
  • Разница между неопределенным поведением и неправильной формой, не требуется диагностическое сообщение
  • Доступ к неактивному члену профсоюза и неопределенному поведению?
  • Неинициализированная локальная переменная - самый быстрый генератор случайных чисел?
  • Реализация C ++, которая обнаруживает неопределенное поведение?
  • Почему этот цикл создает «предупреждение: итерация 3u вызывает неопределенное поведение» и выводит более 4 строк?
  • Как объяснить неопределенное поведение новичков-новичков?
  • Давайте будем гением компьютера.