Страницы

Поиск по вопросам

суббота, 7 декабря 2019 г.

signed int vs unsigned int (undefined behaviour ситуации)

#cpp #c #неопределенное_поведение


Если говорить просто и коротко, то меня интересует: количество и примеры undefined
behaviour для каждого из этих типов.
    


Ответы

Ответ 1



Переполнение при выполнении арифметических операций над типом 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 может быть "запрещено".)

Ответ 2



Добавлю ещё несколько примеров неопределённого поведения и поведения, определяемого реализацией в языке 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 в одном выражении. Естественно, приведённые примеры — это далеко не полный список неопределённых, определяемых реализацией и неспецифицированных поведений :)

Комментариев нет:

Отправить комментарий