Является ли поведение char foo = 255 "undefined, если char подписано?

Следующее не дает мне никакого предупреждения при компиляции с gcc 4.5.2 на машине x86 с Linux:

char foo = 255;

Но когда я использую -pedantic, gcc говорит:

предупреждение: переполнение в неявном постоянном преобразовании

То, как gcc действует, немного странно, и это заставляет меня сомневаться, действительно ли я понимаю, что происходит в этом задании. Я думаю, что если char длится 8 бит в POSIX, и он по умолчанию подписан, он не может удерживать 255.

В стандарте C он говорит, что переполнение целых чисел без знака приводит к переполнению, но полное переполнение целых чисел undefined. Так это назначение undefined поведения? И почему gcc действует таким образом?

Ответ 1

Сводка: результат определяется реализацией и, скорее всего, будет -1, но он усложняется, по крайней мере, в принципе.

Правила переполнения различаются для операторов по сравнению с конверсиями, а для подписанных типов - неподписанных типов - и правила преобразования изменены между C90 и C99.

С C90 переполнение оператора со знаками целочисленных операндов ( "переполнение" означает, что математический результат не может быть представлен в типе выражения) имеет поведение undefined. Для целых операндов без знака поведение корректно определяется как обычное обертывание (строго говоря, стандарт не называет это "переполнением" ). Но ваше выражение:

char foo = 255;

не использует каких-либо операторов (= является инициализатором, а не назначением), поэтому в этом случае ничего не применяется.

Если тип char может представлять значение 255 (которое истинно либо из простого char, либо без знака, либо если CHAR_BIT >= 9), то, конечно, поведение корректно определено. Выражение int 255 неявно преобразуется в char. (Так как CHAR_BIT >= 8, этот случай невозможен для вызова беззнакового обхода.)

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

Начиная с C90, результат преобразования определяется реализацией - это означает, что он гарантировал установить foo на некоторое значение в диапазоне типа char, и вы можете определить, что это значение, путем чтения документацию по внедрению, которая должна рассказать вам, как работает преобразование. (Я никогда не видел реализации, где хранимое значение - это что-то иное, чем -1, но любой результат возможен в принципе.)

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

Если компилятор решает сделать последнее, он должен документировать, какой сигнал поднят.

Итак, что происходит, если сигнал, определяемый реализацией? В разделе 7.14 стандарта говорится:

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

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

7.14 также говорится:

Если и когда функция вернется, если значение sig SIGFPE, SIGILL, SIGSEGV или любое другое значение, определяемое реализацией что соответствует расчетному исключению, поведение undefined; в противном случае программа возобновит выполнение в тот момент, когда она была прерываться.

но я не думаю, что это применимо, поскольку переполняющее преобразование не является "вычислительным исключением", поскольку этот термин используется здесь. (Если сигнал, определяемый реализацией, не будет SIGFPE, SIGILL или SIGSEGV - но это будет глупо).

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

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

Ответ 2

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

Переполняющее интегральное преобразование в подписанный тип не вызывает поведения undefined. Вместо этого он создает результат, определяемый реализацией (с возможностью формирования сигнала, определяемого реализацией).

Ответ 3

Согласно C11, 6.3.1.3:

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