Если говорить просто и коротко, то меня интересует: количество и примеры undefined behaviour для каждого из этих типов.
2 Answers
Переполнение при выполнении арифметических операций над типом
signed intприводит к неопределенному поведению.Объектные представления как
signed int, так иunsigned intмогут иметь в своем составе padding биты, т.е. биты, не участвующие в формировании значения, а либо выполняющие вспомогательные функции, либо вообще не использующиеся. Комбинация значений padding битов может быть некорректной, т.е. формировать так называемые trap representations. Попытка доступа к trap representation приводит к неопределенному поведению. (Например, объектное представление целочисленного типа может содержать биты четности.)Язык, однако, гарантирует, что установка всех битов объектного представления целочисленного типа в
0(в т.ч. padding битов) не может создать trap representation, а всегда приводит к формированию корректно представленного целочисленного значения0. С практической точки зрения это означает, чтоmemset(..., 0, ...)иcallocгарантированно формируют правильные нулевые значения для любых целочисленных типов.Преобразование значений с плавающей точкой или значений указателей к любому целочисленному типу приводят к неопределенному поведению, если результирующее значение не помещается в диапазон целевого типа.
Реализации, основанные на прямом или обратном коде для
signed int, имеют право запретить использование отрицательного нуля, т.е. расценивать представление отрицательного нуля как trap representation. В таком случае доступ к представлению отрицательного нуля приводит к неопределенному поведению.Реализации, основанные на дополнительном коде для
signed int, имеют право запретить использование представления с1в знаковом бите и с0во всех значащих битах, т.е. расценивать это представление как trap representation. В таком случае доступ к такому представлению приводит к неопределенному поведению. (Другими словами, в 16-битномsigned intзначение-32768может быть "запрещено".)
- 69,346
-
Почему при выполнении арифметических операциях над типом signed int приводит к неопределенному поведению? Чем это выражается? И почему при unsigned int нет неопределенного состояния? – MaximPro Mar 02 '17 at 10:45
-
И не понятен последний абзац. Что значит запретить использование представления. Как можно запретить использование дополнительного кода? И что тогда получится? Я ничего не понимаю. Объясните – MaximPro Mar 02 '17 at 15:03
-
@MaximPro: "приводит к неопределенному поведению" потому что так сказано в стандарте. А в стандарте так сказано потому, что 1) в рамках разнообразия представлений никакого способа определить результат нет, 2) существуют/существовали платформы, которые отлавливают знаковые переполнения и выкидывают ошибку, 3) еще масса разных причин, которые все и не упомнишь. – AnT stands with Russia Mar 02 '17 at 15:40
-
@MaximPro: Где вы увидели "запретить использование дополнительного кода" мне не ясно. Речь идет о запрете конкретных комбинаций битов. Попытаетесь прочитать из памяти такую запретную комбинацию, как значение типа
int- получите неопределенное поведение. – AnT stands with Russia Mar 02 '17 at 15:42 -
Я несколько неправильно выразил мысль.У вас написано trap representation. Что расцеивается как этот "trap"? Цитата:
Реализации, основанные на дополнительном коде для signed int, имеют право запретить использование представления с 1 в знаковом бите и с 0 во всех значащих битах. Погодите, но это же дополнительный код как можно расценивать 1000 0000 0000 0000 как trap? Я бы понял если бы это был прямой код или обратный, но тут же...как??? – MaximPro Mar 02 '17 at 17:12 -
@MaximPro: "Trap" - это исторический термин, обозанчающий "исключение" и/или "прерывание". В чем вы видите проблему с
10...0мне в упор не ясно. Любая реализация с дополнительным кодом может сказать: у нас типsigned intимеет диапазон не[-32768, +32767], а диапазон[-32767, +32767](симметричный). А бывшее представление для-32768(т.е.10...0) мы резервируем в качестуве trap-представления. При попытке работы с эти предствлением будет вызникать исключение ("trap"). Вот и все. Что тут непонятного? – AnT stands with Russia Mar 02 '17 at 17:47 -
А зачем этот trap? Нафига он тут нужен? С таким успехом можно и 1111 1111 1111 1111 в трап записать или еще что-нибудь? Я просто не вижу логического смысла в этом? Зачем запрещать код 1000 0000 0000 0000? Когда сказано что в обратном представлении это число -32768. – MaximPro Mar 02 '17 at 18:02
-
@MaximPro: Во-первых, зачем он нужен - догадайтесь сами. Существует миллион хороших причин иметь "зарезервированое плохое значение". Например, для целей пометки неинициализированных или неактульных значений (отдаленно аналогично тот же null-указателю). – AnT stands with Russia Mar 02 '17 at 18:13
-
Во-вторых, где это сказано, что
100..0в доп.коде - это-32768? Каким образом вы это вычислили? На самом деле, нет однозначного ответа на вопрос, обозначает ли100..0-32768или все таки+32768. Можно привести агрументы в пользу обеих интерпретаций. Одно правило смены знака говорит, что надо взять дополнение до2^N. Другое правило смены знака говорит, что надо инвертировать значащие биты и прибавить к результату 1. Оба правила переполняются при работе с числом-32768в 16 битах. – AnT stands with Russia Mar 02 '17 at 18:19 -
Ну там не совсем -32768, но вот допустим тут https://ru.wikipedia.org/wiki/%D0%94%D0%BE%D0%BF%D0%BE%D0%BB%D0%BD%D0%B8%D1%82%D0%B5%D0%BB%D1%8C%D0%BD%D1%8B%D0%B9_%D0%BA%D0%BE%D0%B4_(%D0%BF%D1%80%D0%B5%D0%B4%D1%81%D1%82%D0%B0%D0%B2%D0%BB%D0%B5%D0%BD%D0%B8%D0%B5_%D1%87%D0%B8%D1%81%D0%BB%D0%B0) и в доп.коде так и написано 1000 0000 (-128). Что значит:
Оба правила переполняются при работе с числом -32768 в 16 битах? – MaximPro Mar 02 '17 at 18:20 -
Т.е. ясно, что это представление - "кривоватое". Уже сам факт того, что оно не имеет симметричного положительного "брата" и вызывает переполнение при умножении или делении на
-1говорит о его "кривости". – AnT stands with Russia Mar 02 '17 at 18:20 -
Ну, а что насчет unsigned типа? В каком он виде представляется, я как посмотрю для него нет неопределенного поведения. – MaximPro Mar 02 '17 at 18:33
-
@MaximPro: Беззнаковые типы представляются в чистой двоичной записи (pure binary notation). Никакие "дополнительные коды", "обратные коды" и т.п. никакого отношения к беззнаковым типам не имеют - они там нинафиг не нужны. – AnT stands with Russia Mar 02 '17 at 18:47
-
-
@MaximPro: Ым.. "Прямой код", "обратный код", "дополнительный код" - это все, по определению, способы представления знаковых значений в двоичном коде. Все эти представления фундаментально основаны на использовании знакового бита. К беззнаковым значениям они вообще никаким боком не относятся и не могут относиться. Хотя бы потому, что там никакого знакового бита нет и в помине. – AnT stands with Russia Mar 02 '17 at 18:56
Добавлю ещё несколько примеров неопределённого поведения и поведения, определяемого реализацией в языке C++, которые могут возникнуть не только при работе с целыми числами, но и в смежных ситуациях (например, арифметика указателей).
Битовые сдвиги (далее, результирующий тип — это тип левого операнда, подвергнутый целочисленному продвижению (integral promotion)). ([expr.shift] 8.5.7)
Если правый операнд битового сдвига отрицательный, больше или равен длине в битах продвинутого (promoted) левого операнда — UB.
Если отрицательная знаковая величина сдвигается влево — UB.
Если неотрицательная знаковая величина
E1сдвигается влево наE2бит, и значениеE1 * (2^E2)не может быть представлено в беззнаковом целочисленном типе, соответствующем результирующему типу, то UB.Если неотрицательная знаковая величина
E1сдвигается влево наE2бит, и значениеE1 * (2^E2)может быть представлено в беззнаковом целочисленном типе, соответствующем результирующему типу, но не может быть представлено в результирующем типе, то значениеE1 * (2^E2)преобразуется к результирующему типу. Результат такого преобразования определяется реализацией.Если отрицательная знаковая величина сдвигается вправо, то результирующее значение определяется реализацией.
Если в результате некоторой битовой операции (не только сдвиги) генерируется trap representation, то поведение такой битовой операции не определено. (C 6.2.6.2 / 4)
Арифметика указателей. ([expr.add] 8.5.6)
Если выражение
Pуказывает на элементx[i]массиваxизnэлементов, то выраженияP + JиJ + P(гдеJимеет целочисленное значениеj) указывает на элементx[i + j]только если0 <= i + j <= n, в противном случае — UB.Выражение
P - Jуказывает на элементx[i - j]только если0 <= i - j <= n, в противном случае — UB.Если выражение
Pуказывает на элементx[i]массиваx, а выражениеQуказывает на элементx[j]этого же массива, то результат выраженияP - Qравен знаковому целочисленному значениюi - jтипаstd::ptrdiff_t. ЕслиPиQуказывают на элементы разных массивов — UB. Если числовое значениеi - jне представимо типомstd::ptrdiff_t— UB. Стандарт языка определяет диапазон значений типаstd::ptrdiff_tссылаясь на стандарт языка C, согласно которому этот тип должен вместить все значения из диапазона[-65535, 65535]. Стандарт не требует, чтобыstd::ptrdiff_tмог вместить все значения типаsize_t. ([cstdint.syn] 21.4.1, C 7.20.3 / 2)
Указатели.
Когда время жизни некоторой области памяти подходит к концу, то все указатели, указывающие на любую часть этой области памяти становятся недействительными (invalid pointer value). Разыменование или высвобождение памяти по такому указателю — UB. ([basic.stc] 6.6.4 / 4)
Последствия любого другого использования invalid pointer value — определяются реализацией. В частности, в стандарте явно оговорено, что аварийное завершение работы программы при выполнении следующего кода — нормальное поведение ([basic.stc] 6.6.4 35)):
int *p1, *p2; p1 = new int; delete p1; p2 = p1; //system-generated runtime fault;Если указатель на объектный тип
T1приводится к указателю на объектный типT2, и требования по выравниванию (alignment requirements) для типаT2не выполняются, то результирующее указательное значение не специфицируется (unspecified). Полагаю, в частности, может получиться invalid pointer value со всеми вытекающими. ([expr.reinterpret.cast] 8.5.1.10 / 7, [expr.static.cast] 8.5.1.9 / 13)
Ну и если речь зашла о неопределённом поведении, то как не упомянуть следующие пункты:
Требования strict aliasing rules.
Неопределённое поведение, связанное с неупорядоченностью (unsequenced) value computations и side effects в одном выражении.
Естественно, приведённые примеры — это далеко не полный список неопределённых, определяемых реализацией и неспецифицированных поведений :)
- 6,221
signed. – Harry Mar 02 '17 at 04:275 = 00000101, -5 = 10000101, очевидно, что тут возникает нуль со знаком10000000– vp_arth Mar 02 '17 at 07:12constсconst-объекта и его использование = UB, сдвиг на слишком большое количество битов = UB, два вызоваdeleteподряд = UB, невозврат значения из функции = UB (а не ошибка компиляции), присвоение одного поля вunionи чтение другого = UB, вычитание указателей внутрь разных массивов = UB, делениеMININTна-1= UB, это только то, что вспомнилось навскидку. – VladD Mar 02 '17 at 20:08снятие const с const-объекта и его использование = UB? Подробнее, не понял это как? – MaximPro Mar 03 '17 at 04:57const T, вы получаете на него указатель типаconst T *, снимаете с него константность черезconst_cast, и через полученный указатель модифицируете, это UB. А вот если объект не был декларирован какconst T, тогда UB нет. Но в точке, где вы делаетеconst_cast, часто вы не знаете, был ли объект на деле константой. – VladD Mar 03 '17 at 08:12struct A { int b; }; const A a; const A* pa = &a; const_cast<A*>(pa)->b = 5;— здесь UB. – VladD Mar 03 '17 at 23:21