Основна відповідь - неправильне (але поширене) помилкове уявлення:
Невизначена поведінка є час виконання нерухомості *. Це НЕ МОЖЕ "подорожувати у часі"!
Деякі операції визначені (за стандартом), що мають побічні ефекти і не можуть бути оптимізовані. Операції, що виконують введення-виведення або доступvolatile
цієї категорії належать змінні .
Однак є застереження: UB може бути будь-якою поведінкою, включаючи поведінку, яка скасовує попередні операції. У деяких випадках це може мати подібні наслідки для оптимізації попереднього коду.
Насправді це узгоджується з цитатою у верхній відповіді (наголос мій):
Відповідна реалізація, що виконує добре сформовану програму, повинна виробляти таку ж спостережувану поведінку, як одне з можливих виконання відповідного екземпляра абстрактної машини з тією ж програмою та однаковим входом.
Однак, якщо будь-яке таке виконання містить невизначену операцію, цей Міжнародний стандарт не встановлює вимог до реалізації, що виконує цю програму з цим входом (навіть щодо операцій, що передують першій невизначеній операції).
Так, ця цитата робить говорить «навіть не стосовно операцій , що передують першому невизначеною операції» , але зверніть увагу , що це саме про те , що код в даний час виконується , а не просто компіляції.
Зрештою, невизначена поведінка, якої насправді не досягнуто, нічого не робить, і для того, щоб рядок, що містить UB, був фактично досягнутий, код, що передує йому, повинен виконатись першим!
Так що так, після виконання UB будь-які ефекти попередніх операцій стають невизначеними. Але поки цього не станеться, виконання програми є чітко визначеним.
Однак зауважте, що всі виконання програми, що призводять до цього, можуть бути оптимізовані до еквівалентних програм, включаючи будь-які, які виконують попередні операції, але потім скасовують їх ефекти. Отже, попередній код може бути оптимізований, коли це буде еквівалентно скасуванню їхніх ефектів ; інакше не може. Див. Приклад нижче.
* Примітка: Це не суперечить UB, що виникає під час компіляції . Якщо компілятор дійсно може довести , що UB код буде завжди виконуватися для всіх входів, то UB може поширюватися на час компіляції. Однак для цього потрібно знати, що весь попередній код врешті-решт повертається , що є суворою вимогою. Знову ж, див. Нижче приклад / пояснення.
Щоб зробити це конкретним, зверніть увагу, що наступний код повинен друкуватись foo
і чекати вашого введення, незалежно від будь-якої невизначеної поведінки, що слідує за ним:
printf("foo")
getchar()
*(char*)1 = 1
Однак також зауважте, що немає жодної гарантії, що foo
залишиться на екрані після виникнення UB, або що введений вами символ більше не буде у вхідному буфері; обидві ці операції можна "скасувати", що має подібний ефект до UB "подорож у часі".
Якби getchar()
рядка там не було, було б законно оптимізувати рядки тоді і тільки тоді , коли це було б неможливо відрізнити від виводу, foo
а потім " відмовитись " від цього.
Чи не можна буде розрізнити ці два, цілком залежатиме від реалізації (тобто від вашого компілятора та стандартної бібліотеки). Наприклад, чи можна printf
заблокувати ваш потік тут, очікуючи, поки інша програма прочитає вихідні дані? Або повернеться негайно?
Якщо вона може заблокувати тут, тоді інша програма може відмовитись прочитати повний результат, і вона може ніколи не повернутися, і, отже, UB може ніколи не відбутися.
Якщо він може негайно повернутися сюди, тоді ми знаємо, що він повинен повернутися, і тому його оптимізацію абсолютно неможливо відрізнити від його виконання, а потім скасування його наслідків.
Звичайно, оскільки компілятор знає, яка поведінка допустима для його конкретної версії printf
, він може відповідно оптимізувати і, отже, printf
може бути оптимізований в деяких випадках, а не в інших. Але, знову ж таки, виправдання полягає в тому, що це неможливо відрізнити від UB, який не робив попередніх операцій, а не в тому, що попередній код "отруєний" через UB.
a
не використовується (за винятком самого обчислення), і просто видалитиa