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