Скажем, я собираюсь скомпилировать какой-то плохо написанный исходный код C ++, который вызывает неопределенное поведение, и поэтому (как говорится) «все может случиться».
С точки зрения того, что спецификация языка C ++ считает приемлемым для "совместимого" компилятора, "что-нибудь" в этом сценарии включает сбой компилятора (или кражу моих паролей, или иное некорректное поведение или ошибку во время компиляции), или объем неопределенного поведения ограничен конкретно тем, что может произойти при запуске результирующего исполняемого файла?
c++
language-lawyer
undefined-behavior
Джереми Фриснер
источник
источник
Ответы:
Нормативное определение неопределенного поведения выглядит следующим образом:
Хотя сама заметка не является нормативной, она описывает ряд известных реализаций поведения. Таким образом, сбой компилятора (который приводит к внезапному завершению трансляции) является законным в соответствии с этим примечанием. Но на самом деле, как сказано в нормативном тексте, стандарт не накладывает никаких ограничений ни на исполнение, ни на перевод. Если реализация крадет ваши пароли, это не является нарушением какого-либо контракта, изложенного в стандарте.
источник
Большинство типов UB, о которых мы обычно беспокоимся, например NULL-deref или деление на ноль, являются UB времени выполнения . Компиляция функции, которая при выполнении вызовет UB среды выполнения, не должна вызывать сбой компилятора. Если, возможно, он не докажет, что функция (и этот путь через функцию) определенно будет выполняться программой.
(Вторая мысль: возможно, я не учел обязательную оценку шаблона / constexpr во время компиляции. Возможно, UB во время этого может вызывать произвольные странности во время перевода, даже если результирующая функция никогда не вызывается.)
Behaving во время перевода части в ISO C ++ цитаты в @ ответ рассказчика похож на язык , используемый в стандарте ISO C. C не включает шаблоны или
constexpr
обязательный eval во время компиляции.Но забавный факт : ISO C говорит в примечании, что если перевод прерывается, это должно быть с диагностическим сообщением. Или «вести себя во время перевода ... задокументированным образом». Я не думаю, что «полное игнорирование ситуации» можно толковать как включая прекращение перевода.
Старый ответ, написанный до того, как я узнал о времени перевода UB. Однако это верно для runtime-UB и, следовательно, потенциально все еще полезно.
Там нет такого понятия , как UB , что происходит во время компиляции. Он может быть виден компилятору на определенном пути выполнения, но в терминах C ++ этого не произошло, пока выполнение не достигнет этого пути выполнения через функцию.
Дефекты в программе, которые делают невозможным даже компиляцию, не являются UB, это синтаксические ошибки. Такая программа является «некорректной» в терминологии C ++ (если я правильно придерживаюсь своего стандарта). Программа может быть правильно сформированной, но содержать UB. Разница между неопределенным поведением и неправильно сформированным, диагностическое сообщение не требуется
Если я чего-то не понимаю, ISO C ++ требует, чтобы эта программа компилировалась и выполнялась правильно, потому что выполнение никогда не достигает деления на ноль. (На практике ( Godbolt ) хорошие компиляторы просто создают рабочие исполняемые файлы. Gcc / clang предупреждает,
x / 0
но не об этом, даже при оптимизации. Но в любом случае мы пытаемся сказать, насколько низким ISO C ++ допускает качество реализации. Итак, проверяем gcc / clang вряд ли является полезным тестом, кроме как подтвердить, что я правильно написал программу.)int cause_UB() { int x=0; return 1 / x; // UB if ever reached. // Note I'm avoiding x/0 in case that counts as translation time UB. // UB still obvious when optimizing across statements, though. } int main(){ if (0) cause_UB(); }
Вариант использования для этого может включать препроцессор C или
constexpr
переменные и ветвление по этим переменным, что приводит к бессмыслице в некоторых путях, которые никогда не достигаются при таком выборе констант.Можно предположить, что пути выполнения, которые вызывают видимый во время компиляции UB, никогда не выполняются, например, компилятор для x86 может выдать
ud2
(вызвать исключение недопустимой инструкции) в качестве определения дляcause_UB()
. Или внутри функции, если одна сторонаif()
ведет к доказуемому UB, ветвь может быть удалена.Но компилятор по-прежнему должен правильно и разумно компилировать все остальное . Все пути, которые не встречаются (или не может быть доказано, что встречаются) UB, все равно должны быть скомпилированы в asm, который выполняется, как если бы абстрактная машина C ++ выполняла его.
Вы можете утверждать, что безусловный UB, видимый во время компиляции,
main
является исключением из этого правила. Или иначе можно доказать во время компиляции, что выполнение, начиная сmain
, действительно достигает гарантированного UB.Я бы по-прежнему утверждал, что законное поведение компилятора включает создание гранаты, которая взрывается при запуске. Или, что более вероятно, определение
main
этого состоит из одной недопустимой инструкции. Я бы сказал, что если вы никогда не запускаете программу, то UB еще не было. Сам компилятор не может взорваться, ИМО.Функции, содержащие возможные или доказуемые UB внутри ветвей
UB на любом заданном пути выполнения возвращается назад во времени, чтобы «загрязнить» весь предыдущий код. Но на практике компиляторы могут воспользоваться этим правилом только тогда, когда они действительно могут доказать, что пути выполнения приводят к UB, видимому во время компиляции. например
int minefield(int x) { if (x == 3) { *(char*)nullptr = x/0; } return x * 5; }
Компилятор должен создать asm, который работает для всех,
x
кроме трех, до техx * 5
пор, пока не вызывает подписанное переполнение UB в INT_MIN и INT_MAX. Если эта функция никогда не вызываетсяx==3
, программа, конечно, не содержит UB и должна работать так, как написано.С таким же успехом мы могли бы написать
if(x == 3) __builtin_unreachable();
на GNU C, чтобы сообщить компилятору, чтоx
это точно не 3.На практике в обычных программах повсюду встречается код "минного поля". например, любое деление на целое число обещает компилятору, что оно не равно нулю. Любой указатель deref обещает компилятору, что он не равен NULL.
источник
Что здесь означает «законный»? Все, что не противоречит стандарту C или стандарту C ++, является законным в соответствии с этими стандартами. Если вы выполняете заявление,
i = i++;
и в результате динозавры захватывают мир, это не противоречит стандартам. Однако это противоречит законам физики, поэтому этого не произойдет :-)Если поведение undefined приводит к сбою вашего компилятора, это не нарушает стандарты C или C ++. Однако это означает, что качество компилятора может (и, вероятно, должно) быть улучшено.
В предыдущих версиях стандарта C были утверждения, которые были ошибками или не зависели от неопределенного поведения:
char* p = 1 / 0;
Допускается присвоение константе 0 символу *. Допуск ненулевой константы - нет. Поскольку значение 1/0 является неопределенным поведением, это неопределенное поведение, должен или не должен компилятор принимать этот оператор. (В настоящее время 1/0 больше не соответствует определению «целочисленное постоянное выражение»).
источник