Главный ответ - неправильное (но распространенное) заблуждение:
Неопределенное поведение - это свойство времени выполнения *. Это НЕ МОЖЕТ "путешествовать во времени"!
Некоторые операции определены (по стандарту) как имеющие побочные эффекты и не могут быть оптимизированы. В 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