La respuesta principal es un concepto erróneo (pero común):
El comportamiento no definido es una propiedad en tiempo de ejecución *. ¡ NO PUEDE "viajar en el tiempo"!
Ciertas operaciones están definidas (por el estándar) para tener efectos secundarios y no se pueden optimizar. Las operaciones que realizan E / S o que acceden a volatile
variables entran en esta categoría.
Sin embargo , hay una advertencia: UB puede ser cualquier comportamiento, incluido el comportamiento que deshaga operaciones anteriores. Esto puede tener consecuencias similares, en algunos casos, a la optimización del código anterior.
De hecho, esto es consistente con la cita en la respuesta principal (énfasis mío):
Una implementación conforme que ejecuta un programa bien formado producirá el mismo comportamiento observable que una de las posibles ejecuciones de la instancia correspondiente de la máquina abstracta con el mismo programa y la misma entrada.
Sin embargo, si tal ejecución contiene una operación no definida, esta Norma Internacional no impone ningún requisito a la implementación que ejecuta ese programa con esa entrada (ni siquiera con respecto a las operaciones que preceden a la primera operación no definida).
Sí, esta cita hace decir "ni siquiera en lo que respecta a las operaciones anteriores a la primera operación indefinido" , pero aviso de que se trata específicamente sobre el código que está siendo ejecutado , no sólo compila.
Después de todo, el comportamiento indefinido que no se alcanza en realidad no hace nada, y para que se alcance la línea que contiene UB, ¡el código que la precede debe ejecutarse primero!
Entonces sí, una vez que se ejecuta UB , cualquier efecto de las operaciones anteriores se vuelve indefinido. Pero hasta que eso suceda, la ejecución del programa está bien definida.
Sin embargo, tenga en cuenta que todas las ejecuciones del programa que provoquen que esto suceda se pueden optimizar para programas equivalentes , incluidos los que realizan operaciones anteriores pero luego deshacen sus efectos. En consecuencia, el código anterior puede optimizarse siempre que hacerlo equivalga a deshacer sus efectos ; de lo contrario, no puede. Vea a continuación un ejemplo.
* Nota: Esto no es incompatible con UB que ocurre en tiempo de compilación . Si el compilador puede incluso resultar que el código UB será cuestión se realizará para todas las entradas, a continuación, UB se puede extender a tiempo de compilación. Sin embargo, esto requiere saber que todo el código anterior eventualmente regresa , lo cual es un requisito importante. Nuevamente, vea a continuación un ejemplo / explicación.
Para que esto sea concreto, tenga en cuenta que el siguiente código debe imprimirse foo
y esperar su entrada independientemente de cualquier comportamiento indefinido que le siga:
printf("foo")
getchar()
*(char*)1 = 1
Sin embargo, también tenga en cuenta que no hay garantía de que foo
permanecerá en la pantalla después de que se produzca la UB, o que el carácter que escribió ya no estará en el búfer de entrada; Ambas operaciones se pueden "deshacer", lo que tiene un efecto similar al "viaje en el tiempo" de UB.
Si la getchar()
línea no estuviera allí, sería legal que las líneas se optimizaran si y solo si eso fuera indistinguible de generar foo
y luego " deshacer ".
Si los dos serían indistinguibles o no dependería enteramente de la implementación (es decir, de su compilador y biblioteca estándar). Por ejemplo, ¿puede printf
bloquear su hilo aquí mientras espera que otro programa lea el resultado? ¿O volverá inmediatamente?
Si se puede bloquear aquí, entonces otro programa puede negarse a leer su salida completa y es posible que nunca regrese y, en consecuencia, UB nunca ocurra.
Si puede regresar inmediatamente aquí, entonces sabemos que debe regresar y, por lo tanto, optimizarlo es completamente indistinguible de ejecutarlo y luego deshacer sus efectos.
Por supuesto, dado que el compilador sabe qué comportamiento está permitido para su versión particular de printf
, puede optimizar en consecuencia y, en consecuencia, printf
puede optimizarse en algunos casos y no en otros. Pero, de nuevo, la justificación es que esto sería indistinguible de las operaciones anteriores de deshacer UB, no que el código anterior esté "envenenado" debido a UB.
a
no se usa (excepto para calcularlo a sí mismo) y simplemente eliminarloa