22

Può ottimizzazione del compilatore eliminare un loop infinito, che non cambia alcun dato, comeI compilatori sono autorizzati a eliminare loop infiniti?

while(1) 
    /* noop */; 

Da analizzando un grafico compilatore flusso di dati può derivare, che tale ciclo è "codice morto" senza effetti collaterali.

La cancellazione di loop infiniti è vietata dalle norme C90/C99?

Gli standard C90 o C99 consentono al compilatore di cancellare tali loop?

Aggiornamento: "Microsoft C versione 6.0 ha fatto essenzialmente questa ottimizzazione.", Vedi link di caf.

label: goto label; 
return 0; 

sarà trasformato per

return 0; 
+2

È 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

+0

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

+3

"Ottimizzazione loop senza fine" == "come fare un circuito senza fine finiscono più veloce" –

risposta

19

C11 chiarisce la risposta a questa domanda, nel progetto di sezione standard C11 6.8.5istruzioni 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.4esecuzioni 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.

+1

Sembra che la dicitura C11 implichi che 'while (1,1)/* no-op * /;' * può * essere ottimizzato, poiché l'introduzione di un operatore virgola significa che non è più un'espressione costante. – caf

+0

@caf Accetto * Le espressioni costanti non devono contenere l'assegnazione, incremento, decremento, la funzione chiamata, o operatori virgola * –

+0

Come su 'loop goto'? –

3

Essi sono una necessità quando si scrive demoni. Perché volevi chiamarli codice morto?

+5

Questo non sarebbe un demone molto carino da quando è (1); tecnicamente non mette il thread in sleep e richiede molto tempo. Meglio sarebbe qualcosa come mentre (1) Sleep (5000); o qualcosa di simile – Toad

+0

Questo è un problema secondario. Ad ogni modo, 'while (1)' è tutto tranne _dead code_ - che era il mio punto. E sì, penso di aver visto qualche strumento di analisi del codice riportarlo come tale - un ciclo infinito. – dirkgently

+2

Ma 'while (1);' * è * essenzialmente codice morto - non fa nulla di utile tranne il blocco della CPU. Molto meglio avere qualcosa come: char exiting = 0; while (! Exiting) { exiting = process_event(); } – TMN

8

Il ciclo non è un codice morto, praticamente impedisce al programma di raggiungere qualsiasi cosa venga dopo. Questo non è ciò che accadrebbe se il ciclo fosse rimosso, quindi il compilatore non può rimuovere il ciclo.

Potrebbe sostituirlo con un'istruzione inattiva dipendente dalla piattaforma per segnalare al processore che il thread non farà più nulla.

Ciò che il compilatore può fare è rimuovere qualsiasi codice che viene dopo il ciclo, perché non è raggiungibile e non verrà mai eseguito.

9

Non c'è modo di rilevare cicli infiniti universalmente: vedere the Halting Problem. Quindi il miglior compilatore che potrebbe fare è prendere una decisione ragionevole - per esempio il caso ovvio menzionato nell'OP.

Ma perché sarebbe auspicabile? Potevo vedere emettere un avvertimento e permetterne comunque il comportamento, ma rimuovere il ciclo non è un "ottimizzazione" - cambia il comportamento del programma!

+2

La prima regola del problema dell'arresto è che nessuno menziona il problema dell'arresto. –

+7

Sebbene sia impossibile per i programmi arbitrari, è certamente possibile per cicli abbastanza banali come questo. – fennec

4

Questo è stato discusso molte volte prima su comp.lang.c (ad esempio here) senza, per quanto ne so, alcun risultato di consenso.

+0

Grazie! Puoi trovare altri link a tali thread in usenet? – osgx

+8

Sono sicuro che puoi usare Google il meglio che posso. – caf

0

Credo che gli standard più recenti stabiliscano esplicitamente che se un pezzo di codice non accede a nessuna variabile volatile, esegue I/O, ecc. Qualsiasi altro codice che non si basa su nulla calcolato nel primo pezzo può essere sequenziato arbitrariamente prima esso. Se un ciclo infinito non esegue alcun I/O, né calcola alcun valore che viene utilizzato successivamente nel programma, un compilatore può semplicemente rinviare l'esecuzione del ciclo fino a quando tutto il resto non è completato.

+0

Puoi credere anche in Dio, ma non ci può essere alcun standard ISO del comportamento di Dio. Esistono stadards in linguaggio C che consentono questo? – osgx

+1

@osgx: Si consideri il codice "int main (void) {fai_qualcosa(); do_something_else(); return 0;}" Supponiamo che il compilatore può guardare il codice e determinare che fa_qualcosa() non scrivere qualsiasi variabile volatili, né scrive qualsiasi variabile che do_something_else() userà mai. L'unica cosa che do_something() potrebbe fare che altererebbe l'output di do_something_else() sarebbe a ciclo infinito. Se a un compilatore fosse permesso solo di rilasciare codice che non poteva girare all'infinito, sarebbe costretto a includere un sacco di codice inutile. Consentire la caduta di loop infiniti facilita le cose. – supercat

+0

c99 5.1.2.3 "Accesso ad un oggetto volatile, modifica di un oggetto, modificando un file, o chiamare una funzione che esegue una di queste operazioni" - è l'unico effetto collaterale. "Cambiano lo stato dell'ambiente di esecuzione". "Un effettiva attuazione non è necessario valutare una parte di un'espressione se può dedurre che il suo valore non viene utilizzato e che effetti collaterali necessari vengono prodotte" – osgx

Problemi correlati