La maggior parte dei tipi di UB di cui ci preoccupiamo solitamente, come NULL-deref o dividere per zero, sono UB a runtime . La compilazione di una funzione che, se eseguita, provocherebbe UB di runtime, non deve causare il crash del compilatore. A meno che non possa provare che la funzione (e quel percorso attraverso la funzione) verrà sicuramente eseguita dal programma.
(2 ° pensiero: forse non ho considerato la valutazione richiesta da template / constexpr in fase di compilazione. Forse durante la compilazione UB può causare stranezze arbitrarie durante la traduzione anche se la funzione risultante non viene mai chiamata.)
Il comportamento durante la traduzione della parte della citazione ISO C ++ nella risposta di @ StoryTeller è simile al linguaggio utilizzato nello standard ISO C. C non include modelli o constexpr
valutazione obbligatoria in fase di compilazione.
Ma curiosità : l'ISO C dice in una nota che se la traduzione viene terminata, deve essere con un messaggio diagnostico. O "comportarsi durante la traduzione ... in modo documentato". Non credo che "ignorare completamente la situazione" possa essere interpretato come un arresto della traduzione.
Vecchia risposta, scritta prima che venissi a conoscenza del tempo di traduzione UB. È vero per runtime-UB, tuttavia, e quindi potenzialmente ancora utile.
Non c'è niente come UB che accade in fase di compilazione. Può essere visibile al compilatore lungo un certo percorso di esecuzione, ma in termini C ++ non è avvenuto fino a quando l'esecuzione non raggiunge quel percorso di esecuzione attraverso una funzione.
I difetti in un programma che rendono impossibile persino la compilazione non sono UB, sono errori di sintassi. Un tale programma "non è ben formato" nella terminologia C ++ (se ho il mio standardese corretto). Un programma può essere ben formato ma contenere UB. Differenza tra comportamento indefinito e malformato, nessun messaggio diagnostico richiesto
A meno che io non stia fraintendendo qualcosa, ISO C ++ richiede che questo programma venga compilato ed eseguito correttamente, perché l'esecuzione non raggiunge mai la divisione per zero. (In pratica ( Godbolt ), i buoni compilatori creano solo eseguibili funzionanti. Gcc / clang avverte di x / 0
questo, ma non questo, anche durante l'ottimizzazione. Ma comunque, stiamo cercando di dire quanto basso ISO C ++ permetta di essere la qualità dell'implementazione. Quindi controllare gcc / clang non è certo un test utile se non per confermare che ho scritto correttamente il programma.)
int cause_UB() {
int x=0;
return 1 / x;
}
int main(){
if (0)
cause_UB();
}
Un caso d'uso per questo potrebbe coinvolgere il preprocessore C, o constexpr
variabili e ramificazioni su quelle variabili, il che porta a sciocchezze in alcuni percorsi che non vengono mai raggiunti per quelle scelte di costanti.
Si può presumere che i percorsi di esecuzione che causano l'UB visibile in fase di compilazione non vengano mai accettati, ad esempio un compilatore per x86 potrebbe emettere ud2
un'eccezione (causa un'eccezione illegale) come definizione di cause_UB()
. Oppure all'interno di una funzione, se un lato di un if()
porta a UB dimostrabile , il ramo può essere rimosso.
Ma il compilatore deve ancora compilare tutto il resto in modo sano e corretto. Tutti i percorsi che non incontrano (o non è possibile provare che incontrino) UB devono comunque essere compilati in asm che viene eseguito come se la macchina astratta C ++ lo stesse eseguendo.
Si potrebbe sostenere che l'UB visibile in fase di compilazione incondizionato in main
è un'eccezione a questa regola. O altrimenti dimostrabile in fase di compilazione che l'esecuzione a partire da main
raggiunge effettivamente UB garantito.
Continuo a sostenere che i comportamenti legali del compilatore includono la produzione di una granata che esplode se eseguita. O più plausibilmente, una definizione di main
ciò consiste in un'unica istruzione illegale. Direi che se non esegui mai il programma, non c'è ancora stato alcun UB. Il compilatore stesso non può esplodere, IMO.
Funzioni contenenti UB possibili o dimostrabili all'interno dei rami
UB lungo un dato percorso di esecuzione arriva indietro nel tempo per "contaminare" tutto il codice precedente. Ma in pratica i compilatori possono trarre vantaggio da questa regola solo quando possono effettivamente dimostrare che i percorsi di esecuzione portano a UB visibili in fase di compilazione. per esempio
int minefield(int x) {
if (x == 3) {
*(char*)nullptr = x/0;
}
return x * 5;
}
Il compilatore deve creare un asm che funzioni per tutti gli x
altri 3, fino ai punti in cui x * 5
causa l'overflow del segno UB a INT_MIN e INT_MAX. Se questa funzione non viene mai chiamata con x==3
, il programma ovviamente non contiene UB e deve funzionare come scritto.
Potremmo anche aver scritto if(x == 3) __builtin_unreachable();
in GNU C per dire al compilatore che x
sicuramente non è 3.
In pratica c'è codice "minato" ovunque nei normali programmi. ad esempio, qualsiasi divisione per un numero intero promette al compilatore che è diverso da zero. Qualsiasi puntatore deref promette al compilatore che non è NULL.