Большинство типов 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;
}
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.