Більшість видів 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 ще не було. Сам компілятор не має права вибухати, IMO.
Функції, що містять можливі або доказові UB всередині гілок
UB на будь-якому заданому шляху виконання рухається назад у часі, щоб "забруднити" весь попередній код. Але на практиці компілятори можуть скористатися цим правилом лише тоді, коли вони насправді можуть довести, що шляхи виконання ведуть до видимого під час компіляції UB. напр
int minefield(int x) {
if (x == 3) {
*(char*)nullptr = x/0;
}
return x * 5;
}
Компілятор повинен зробити asm, який працює для всіх, x
крім 3, аж до тих місць, де x * 5
викликає переповнення UB на INT_MIN та INT_MAX. Якщо цю функцію ніколи не викликати x==3
, програма, звичайно, не містить UB і повинна працювати як написана.
Ми могли б також написати if(x == 3) __builtin_unreachable();
на GNU C, щоб сказати компілятору, що x
це точно не 3.
На практиці в звичайних програмах всюди є код "мінного поля". наприклад, будь-яке ділення на ціле число обіцяє компілятору, що воно не є нульовим. Будь-який покажчик deref обіцяє компілятору, що він не є NULL.