Неопределённое поведение: различия между версиями
[отпатрулированная версия] | [отпатрулированная версия] |
Tucvbif (обсуждение | вклад) |
Tucvbif (обсуждение | вклад) |
||
Строка 22: | Строка 22: | ||
В последних трёх случаях будет корректный результат, однако только последние два можно считать корректными, так как переполнения строго говоря в них не происходит, и следовательно не происходит неопределённого поведения. |
В последних трёх случаях будет корректный результат, однако только последние два можно считать корректными, так как переполнения строго говоря в них не происходит, и следовательно не происходит неопределённого поведения. |
||
Некоторые виды неопределённого поведения могут приводить к ещё более неожиданным результатам, например разыменовывание нулевого указателя в C может привести к вызову частей программы, которые не должны быть вызваны, в том числе недоступных, или наоборот пропуску частей программы. |
Некоторые виды неопределённого поведения могут приводить к ещё более неожиданным результатам, например разыменовывание нулевого указателя в C может привести к вызову частей программы, которые не должны быть вызваны, в том числе недоступных, или наоборот пропуску частей программы. Распространено жаргонное название последствий неопределённого поведения в C, как «носовых демонов», после того, как один из пользователей [[usenet]] объяснил UB как «программа может заставить демонов вылетать из вашего носа»<ref>{{cite web|title=nasal demons|url=http://www.catb.org/jargon/html/N/nasal-demons.html|website=[[Jargon File]]}}</ref>. |
||
== Примеры == |
== Примеры == |
Версия от 15:42, 10 сентября 2023
Неопределённое поведение (англ. undefined behaviour, в ряде источников непредсказуемое поведение[1][2]) — ситуация, когда в определённых маргинальных случаях поведение программного продукта или устройства может меняться неконтролируемым образом и приводить к некорректным результатам, но это не является ошибкой, и о такой возможности указано в спецификации. Как правило, предполагается, что пользователь данного продукта имеет достаточную компетенцию, чтобы избежать этих случаев. Чаще всего речь идёт о неопределённом поведении в языках программирования.
Неопределенное поведение не следует путать с неуточняемым поведением (unspecified behavior), при котором спецификация разрешает не любое поведение, а только ограниченный диапазон вариантов реализации.
Причины
Основные причины, по которым может допускаться неопределённое поведение:
- Технические ограничения;
- Оптимизации с целью снижения расходов на производство, эксплуатацию, повышение быстродействия и снижение потребления ресурсов;
- Возможность нескольких реализаций, например — нескольких разных компиляторов от разных производителей по одной спецификации языка программирования;
- Кроссплатформенность;
- Возможность изменения продукта со временем.
В качестве примера можно привести арифметические операции с переполнением. Например, необходимо вычислить в целых числах значение
При небольших значениях a и b пример всегда решается корректно. Но если превысит максимальный для данной платформы результат, возникнет неопределённость:
- Возможно, на данной конкретной платформе произойдёт исключение или даже аварийное завершение программы;
- Результат примет максимально возможное значение. К примеру, для 16-разрядной системы это будет 32 767, и итогом будет 16 383 (так как используется только целочисленное деление);
- Чаще всего происходит арифметическое переполнение, то есть если равно 17 000, а равно 18 000, в итоге получится −15 268 вместо ожидаемого 17 500.
- Компилятор может по каким-то причинам посчитать, что более оптимальным будет преобразовать выражение так, чтобы деление происходило раньше сложения, или каким-либо другим способом будет обойдён момент переполнения;
- Может использоваться длинная арифметика;
- Программа может быть перенесена на платформу с большим размером слова.
В последних трёх случаях будет корректный результат, однако только последние два можно считать корректными, так как переполнения строго говоря в них не происходит, и следовательно не происходит неопределённого поведения.
Некоторые виды неопределённого поведения могут приводить к ещё более неожиданным результатам, например разыменовывание нулевого указателя в C может привести к вызову частей программы, которые не должны быть вызваны, в том числе недоступных, или наоборот пропуску частей программы. Распространено жаргонное название последствий неопределённого поведения в C, как «носовых демонов», после того, как один из пользователей usenet объяснил UB как «программа может заставить демонов вылетать из вашего носа»[3].
Примеры
В языке Си, к примеру, использование переменной до её инициализации приводит к неопределённому поведению. Согласно спецификации компилятор должен в этом случае сделать что-либо, что может показаться наиболее эффективным/простым. Неопределённое поведение возникает при попытке обращения к переменной.
Библиотеки могут не проверять указатели на NULL для быстродействия.
В процессорах x86, если есть два последовательных порта ввода-вывода и требуется записать информацию сначала в один порт, затем в другой — это следует делать по одному байту, поскольку порядок прихода байтов на оборудование не гарантируется.
Ещё один пример неопределенного поведения: курьёз с ANSI-директивой «#pragma». Согласно спецификации языка, компиляторам предоставлена полная свобода при обработке этой конструкции. До версии 1.17 компилятор GCC при нахождении в исходном коде этой директивы пытался запустить Emacs с игрой «Ханойские башни».[4]
В качестве ещё одного примера неопределённого поведения можно привести код:
int i = 5;
i = ++i + ++i;
При его выполнении переменная i
может принять значения 13 или 14 для C/C++, 13 для Java, PHP и C#, 12 при реализации на LISP. Неопределенность в языках C и C++ связана с тем, что, согласно стандартам C и C++, побочные эффекты (то есть инкремент в данном случае) могут быть применены в любой удобный для компилятора момент между двумя точками следования.
Достоинства
- Определение некоторых операций как «неопределённых» приводит подобные языки (характеризующиеся зачастую отсутствием встроенной проверки на предел массива и т. д.) к упрощению спецификации и некоторому увеличению гибкости.
- Ускоряется работа программ (так как не нужно проверять всевозможные «маргинальные» случаи).
Недостатки
- Не гарантирует полной совместимости различных реализаций языка.
- Недопущение ситуаций неопределённого поведения остаётся за программистом.
Примечания
- ↑ Программирование на языке C/C++. Самоучитель. — Dialektika, 2003-01-01. — 348 с. — ISBN 9785845904607.
- ↑ Павловская Татьяна Александровна. C/C++. Процедурное и объектно-ориентированное программирование. Учебник для вузов. Стандарт 3-го поколения. — "Издательский дом ""Питер""", 2014-07-30. — 496 с. — ISBN 9785496001090.
- ↑ nasal demons . Jargon File.
- ↑ A Pragmatic Decision | D-Mac's Stuff . Дата обращения: 21 марта 2009. Архивировано 1 июня 2009 года.
Ссылки
- Мобильность на уровне исходных текстов Архивная копия от 11 октября 2006 на Wayback Machine
- Разыменовывание нулевого указателя приводит к неопределённому поведению Архивная копия от 2 апреля 2015 на Wayback Machine