Чи правомірно вихідний код, що містить невизначену поведінку, призводить до збою компілятора?


85

Скажімо, я йду скомпілювати якийсь погано написаний вихідний код C ++, який викликає невизначену поведінку, і тому (як вони кажуть) "все може статися".

З точки зору того, що специфікація мови С ++ вважає прийнятною для компілятора "конформний", чи включає "що-небудь" у цьому сценарії збої компілятора (або викрадення моїх паролів, або інше неналежне поводження або помилку під час компіляції), або обсяг невизначеної поведінки, обмежений конкретно тим, що може статися, коли отриманий виконуваний файл запускається?


22
"UB - це UB. Живи з цим" ... Не чекати. "Будь ласка, опублікуйте MCVE." ... Не чекати. Мені подобається питання за всі рефлекси, які він викликає неадекватно. :-)
Юннош

14
Насправді немає обмежень, саме тому кажуть, що УБ може викликати носових демонів .
Якийсь чувак-програміст

15
UB може змусити автора опублікувати запитання на SO. : P
Tanveer Badar

45
Незалежно від того, що говорить стандарт С ++, якби я був автором компіляторів, я б, безумовно, розглядав це як помилку у своєму компіляторі. Тож якщо ви бачите це, подайте звіт про дефект.
Джон

9
@LeifWillerts Це було ще у 80-ті. Я не пам'ятаю точної конструкції, але думаю, що це залежало від використання згорнутих типів змінних. Після того, як я поставив заміну, у мене був момент "про що я думав - все не так працює". Я не звинувачував компілятор у відхиленні конструкції, а лише у перезавантаженні машини. Сумніваюся, що сьогодні хтось зіткнеться з цим компілятором. Це був крос-компілятор HP C для HP 64000, націлений на мікропроцесор 68000.
Аві Бергер

Відповіді:


71

Нормативне визначення невизначеної поведінки таке:

[defns.undefined]

поведінка, щодо якої цей міжнародний стандарт не вимагає жодних вимог

[Примітка: Невизначена поведінка може очікуватися, коли цей Міжнародний стандарт опускає будь-яке явне визначення поведінки або коли програма використовує помилкову конструкцію або помилкові дані. Допустима невизначена поведінка варіюється від ігнорування ситуації повністю з непередбачуваними результатами, до поведінки під час перекладу або виконання програми документально, характерним для середовища (з видачею діагностичного повідомлення або без нього), до припинення перекладу чи виконання (з видачею діагностичного повідомлення). Багато помилкових конструкцій програм не породжують невизначеної поведінки; їм потрібно поставити діагноз. Оцінка константного виразу ніколи не демонструє поведінку, явно вказану як невизначену. - кінцева примітка]

Хоча сама примітка не є нормативною, вона описує цілий ряд реалізацій поведінки, які, як відомо, демонструють. Отже, збій компілятора (який переклад різко припиняється) є законним відповідно до цієї примітки. Але насправді, як сказано в нормативному тексті, стандарт не ставить жодних обмежень ні для виконання, ні для перекладу. Якщо реалізація викраде ваші паролі, це не є порушенням будь-якого договору, викладеного в стандарті.


43
Тим не менш, якщо ви дійсно можете отримати компілятор для виконання довільного коду під час компіляції, без будь-якого пісочниці, тоді різним людям із захисту буде дуже цікаво про це знати. Те саме стосується сегментації компілятора.
Кевін

67
Так само за те, що сказав Кевін. Будучи інженером-компілятором C / C ++ / etc у попередній кар’єрі, наша позиція полягала в тому, що невизначена поведінка може зірвати вашу програму , зіпсувати вихідні дані, підпалити ваш будинок, що завгодно. Але компілятор ніколи не повинен аварійно працювати, незалежно від того, який вхід. (Це може не давати корисних повідомлень про помилки, але воно повинно виробляти якусь діагностику та вихід, а не просто кричати ЧТУЛЬХУ ВЗІМАТИ КОЛЕСО та сегментацію.)
Ті Стрга

8
@TiStrga Б'юся об заклад, Ктулху зробив би чудового водія F1.
zeta-band

35
"Якщо реалізація викраде ваші паролі, це не є порушенням будь-якого договору, викладеного в стандарті." Це правда незалежно від того, чи є в коді UB, правда? Стандарт диктує лише те, що повинна робити скомпільована програма - компілятор, який правильно компілює код, але викрадає ваші паролі в процесі, не буде не слухатися стандарту.
Carmeister

8
@Carmeister, ооо, це добре, я обов'язково нагадую людям про це щоразу, коли з'являються аргументи "UB дає компілятору дозвіл розпочати ядерну війну". Знову ж таки.
ilkkachu

8

Більшість видів 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 ще не було. Сам компілятор не має права вибухати, 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.


3

Що тут означає "законний"? Все, що не суперечить стандарту C або стандарту C ++, є законним відповідно до цих стандартів. Якщо ви виконуєте заяву, i = i++;і в результаті динозаври опановують світ, це не суперечить стандартам. Однак це суперечить законам фізики, тому цього не трапиться :-)

Якщо невизначена поведінка збиває ваш компілятор, це не порушує стандарт C або C ++. Однак це означає, що якість компілятора можна (і, мабуть, слід) покращити.

У попередніх версіях стандарту C існували твердження, які були помилками або не залежали від невизначеної поведінки:

char* p = 1 / 0;

Допускається присвоєння константи 0 символу *. Допускати ненульову константу - це не так. Оскільки значення 1/0 є невизначеною поведінкою, це невизначена поведінка, чи повинен компілятор приймати це твердження чи ні. (В даний час 1/0 більше не відповідає визначенню "цілочисельний постійний вираз").


3
Якщо бути точним: динозаври, що опановують світ, не суперечать жодним законам фізики (наприклад, зміна парку Юрського періоду). Це просто малоймовірно. :)
химерна
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.