C11 chiarisce la risposta a questa domanda, nel progetto di sezione standard C11 6.8.5
istruzioni di iterazione aggiunto il seguente comma:
Una dichiarazione di iterazione la cui espressione di controllo non è una costante espressione, 156) che non esegue operazioni di input/output, non accede a oggetti volatili e non esegue operazioni di sincronizzazione atomiche nel suo corpo, espressione di controllo o (nel caso di un per la dichiarazione) la sua espressione-3, può essere assunta dall'implementazione da terminare. 157)
e la nota 157
dice:
Questo è destinato a consentire le trasformazioni del compilatore, come la rimozione di loop vuote anche quando cessazione non può essere provata.
Così il vostro esempio specifico:
while(1)
/* noop */;
non è un gioco equo per l'ottimizzazione poiché l'espressione di controllo è un'espressione costante.
loop
infinito come UB
Allora, perché sono i compilatori permesso di ottimizzare via cicli infiniti con l'eccezione di cui sopra, così Hans Boehm fornisce una motivazione per fare cicli infiniti comportamento indefinito in: N1528: Why undefined behavior for infinite loops?, la seguente citazione dà un buon feeling per il rilascio coinvolti:
Come N1509 fa giustamente notare, l'attuale progetto dà essenzialmente comportamento indefinito di cicli infiniti in 6.8.5p6. Un grosso problema per è che consente al codice di spostarsi attraverso un loop potenzialmente non terminante . Ad esempio, supponiamo di avere le seguenti cicli, dove conteggio e count2 sono variabili globali (o hanno avuto il loro indirizzo preso), e P è una variabile locale, il cui indirizzo non è stato preso:
for (p = q; p != 0; p = p -> next) {
++count;
}
for (p = q; p != 0; p = p -> next) {
++count2;
}
Could questi due anelli devono essere uniti e sostituiti dal seguente ciclo?
for (p = q; p != 0; p = p -> next) {
++count;
++count2;
}
Senza la dispensazione speciale 6.8.5p6 per cicli infiniti, questo sarebbe invalidato: Se il primo ciclo non termina perché q punti ad una lista circolare, l'originale non scrive a count2. Pertanto, potrebbe essere eseguito in parallelo con un altro thread che accede o con gli aggiornamenti count2. Questo non è più sicuro con la versione trasformata che accede al count2 nonostante il ciclo infinito. Pertanto la trasformazione introduce potenzialmente una corsa di dati.
In casi come questo, è molto improbabile che un compilatore sia in grado di fornire per la conclusione del ciclo; dovrebbe capire che q punti in una lista aciclica, che credo sia oltre la capacità della maggior parte dei compilatori mainstream e spesso impossibile senza l'intero programma informazioni.
C99
Dato C99 non ha questo ritagliarsi, vorremmo guardare al come-se regola coperto nella sezione 5.1.2.3
che sostanzialmente dice che il compilatore ha solo per emulare il comportamento osservabile di un programma, i requisiti sono i seguenti:
dei minimi requisiti su conforme attuazione sono:
- Nei punti di sequenza, gli oggetti volatili sono stabili nel senso che gli accessi precedenti sono completi e gli accessi successivi non si sono ancora verificati.
- Al termine del programma, tutti i dati scritti nei file devono essere identici al risultato che l'esecuzione di del programma in base alla semantica astratta avrebbe prodotto.
- La dinamica di input e output dei dispositivi interattivi deve avvenire come specificato in 7.19.3. Lo scopo di questi requisiti è che l'output non bufferizzato o con buffer di linea venga visualizzato il prima possibile, per garantire che i messaggi di richiesta vengano effettivamente visualizzati prima di un programma in attesa di input.
una rigorosa lettura di questo sembrerebbe consentire un'implementazione per ottimizzare un ciclo infinito di distanza. Possiamo certamente venire con scenari in cui l'ottimizzazione via un ciclo infinito causerebbe un cambiamento nel comportamento osservabile:
while(1) ;
printf("hello world\n") ;
Molti sostengono che effettua la terminazione di un processo è anche comportamento osservabile, questa posizione è presa in C Compilers Disprove Fermat’s Last Theorem:
il compilatore viene proposta una notevole libertà nel modo implementa il programma C, ma la sua uscita deve avere lo stesso comportamento visibile esternamente che il programma avrebbe se interpretato dalla “C macchina astratta” che è descritto nella norma . Molte persone esperte (incluso me) leggono questo dicendo che il comportamento di terminazione di un programma non deve essere cambiato. Ovviamente alcuni redattori di compilatori non sono d'accordo, oppure non credono che sia importante. Il fatto che persone ragionevoli non siano d'accordo sull'interpretazione sembrerebbe indicare che lo standard C è difettoso.
Aggiornamento
ho riesce a sbagliare il follow-up per l'articolo di cui sopra, Compilers and Termination Revisited che dice quanto segue riguardo sezione 5.1.2.3
:
La seconda esigenza è quella difficile.Se si tratta di terminazione del programma in esecuzione sulla macchina astratta, allora viene incontro vacuamente perché il nostro programma non termina. Se si tratta di terminazione del programma effettivo generato dal compilatore, l'implementazione C è errata perché i dati scritti nei file (stdout è un file) differiscono dai dati scritti dalla macchina astratta. (Questa lettura è dovuto ad Hans Boehm, non ero riuscito a prendere in giro questa sottigliezza fuori dello standard.)
Si potrebbe anche fare un ragionamento più debole che la necessità di creare un ritagliarsi in C11 per consentire la rimozione del ciclo vuoto implica che questa non era una ottimizzazione consentita in precedenza.
Questo vale anche per i cicli di goto infiniti?
Credo che l'intento sia che questo si applica anche a cicli infiniti di goto. In C++ questo è chiaramente il caso, poiché sezione 1.10
[intro.multithread] dice:
L'attuazione può assumere che ogni filo finirà effettuare una delle seguenti
- terminare,
- effettuare una chiamata a una libreria I/O,
- accedere o modificare un oggetto volatile oppure
- eseguire un'operazione di sincronizzazione o un operazione omica.
e quindi l'intenzione espressa nel N1528
è che lo standard C e C++ sono d'accordo:
Dal compilatore back-end sono in genere condivisi tra i compilatori C e C++, sembra più importante che WG14 e WG21 concordano su qualunque soluzione venga adottata. Le alternative sarebbero un trattamento speciale delle due lingue da parte del back-end, o disabilitando le ottimizzazioni durante l'elaborazione del codice C. Né sembra affatto desiderabile.
e alla fine dice:
WG21 sta prendendo in considerazione una migliore formulazione che rende il trattamento coerente. Speriamo che il WG14 tenga traccia delle eventuali modifiche risultanti.
Attualmente lo standard C11 non contiene la formulazione simile nella sezione 5.1.2.4
esecuzioni Multi-filettati e supera dati ma considerando N1528
sembra saggio ipotizzare compilatori trattare infinito goto loop comportamento come definito in C e C++.
Nota anche vedere US comment 38 here e N3196 che è la carta da cui è stata applicata questa modifica.
È in attesa, ei demoni non devono usare molto l'attesa. Io chiamo tale costruzione "morta" in mezzo al flusso di dati. Se l'istruzione non modifica alcuna variabile e non contiene effetti collaterali, può essere eliminata ottimizzando il compilatore. – osgx
Il codice dopo il ciclo non è "irraggiungibile", il loop può essere interrotto con il segnale e nel gestore di segnale può essere presente "longjmp". – osgx
"Ottimizzazione loop senza fine" == "come fare un circuito senza fine finiscono più veloce" –