La risposta principale è un'idea sbagliata (ma comune):
Il comportamento indefinito è una proprietà di runtime *. E NON PUO "viaggio nel tempo"!
Alcune operazioni sono definite (dallo standard) per avere effetti collaterali e non possono essere ottimizzate. Le operazioni che eseguono operazioni di I / O o che accedono a volatile
variabili rientrano in questa categoria.
Tuttavia , c'è un avvertimento: UB può essere qualsiasi comportamento, incluso il comportamento che annulla le operazioni precedenti. Ciò può avere conseguenze simili, in alcuni casi, all'ottimizzazione del codice precedente.
In effetti, questo è coerente con la citazione nella risposta in alto (enfasi mia):
Un'implementazione conforme che esegue un programma ben formato produrrà lo stesso comportamento osservabile di una delle possibili esecuzioni dell'istanza corrispondente della macchina astratta con lo stesso programma e lo stesso input.
Tuttavia, se una di tali esecuzioni contiene un'operazione indefinita, la presente norma internazionale non impone alcun requisito all'implementazione che esegue quel programma con quell'input (nemmeno per quanto riguarda le operazioni che precedono la prima operazione non definita).
Sì, questa citazione non dire "nemmeno per quanto riguarda le operazioni che precedono la prima operazione indefinito" , a meno di notare che questo è specificamente sul codice che viene eseguito , non semplicemente compilato.
Dopotutto, un comportamento indefinito che non è effettivamente raggiunto non fa nulla, e affinché la riga contenente UB sia effettivamente raggiunta, il codice che lo precede deve essere eseguito per primo!
Quindi sì, una volta eseguito UB , qualsiasi effetto delle operazioni precedenti diventa indefinito. Ma fino a quando ciò non accade, l'esecuzione del programma è ben definita.
Notare, tuttavia, che tutte le esecuzioni del programma che provocano questo accadimento possono essere ottimizzate per programmi equivalenti , comprese quelle che eseguono operazioni precedenti ma poi annullano i loro effetti. Di conseguenza, il codice precedente può essere ottimizzato ogni volta che farlo equivarrebbe all'annullamento dei loro effetti ; altrimenti non può. Vedi sotto per un esempio.
* Nota: questo non è in contrasto con UB che si verifica in fase di compilazione . Se il compilatore può effettivamente provare che il codice UB verrà sempre eseguito per tutti gli input, allora UB può estendersi al tempo di compilazione. Tuttavia, ciò richiede la consapevolezza che tutto il codice precedente alla fine ritorna , il che è un requisito fondamentale. Di nuovo, vedi sotto per un esempio / spiegazione.
Per rendere questo concreto, nota che il codice seguente deve essere stampato foo
e attendere il tuo input indipendentemente da qualsiasi comportamento non definito che lo segue:
printf("foo")
getchar()
*(char*)1 = 1
Tuttavia, nota anche che non c'è alcuna garanzia che foo
rimarrà sullo schermo dopo che si è verificato l'UB o che il carattere che hai digitato non sarà più nel buffer di input; entrambe queste operazioni possono essere "annullate", il che ha un effetto simile al "viaggio nel tempo" di UB.
Se la getchar()
linea non fosse presente, sarebbe legale che le linee venissero ottimizzate se e solo se ciò fosse indistinguibile dall'emissione foo
e quindi "annullamento".
Il fatto che i due siano indistinguibili o meno dipenderà interamente dall'implementazione (cioè dal compilatore e dalla libreria standard). Ad esempio, puoi printf
bloccare il tuo thread qui mentre aspetti che un altro programma legga l'output? O tornerà immediatamente?
Se può bloccare qui, allora un altro programma può rifiutarsi di leggere il suo output completo, e potrebbe non tornare mai più, e di conseguenza UB potrebbe non verificarsi mai.
Se può tornare immediatamente qui, allora sappiamo che deve tornare, e quindi ottimizzarlo è del tutto indistinguibile dall'eseguirlo e quindi annullare i suoi effetti.
Naturalmente, poiché il compilatore sa quale comportamento è consentito per la sua particolare versione di printf
, può ottimizzare di conseguenza e di conseguenza printf
può essere ottimizzato in alcuni casi e non in altri. Ma, ancora una volta, la giustificazione è che questo sarebbe indistinguibile dall'annullamento di operazioni precedenti da parte di UB, non che il codice precedente sia "avvelenato" a causa di UB.
a
non è utilizzato (tranne che per il calcolo stesso) e rimuoverlo semplicementea