Ottimizzare un "while (1);" in C ++ 0x


153

Aggiornato, vedi sotto!

Ho sentito e letto che C ++ 0x consente a un compilatore di stampare "Hello" per il seguente frammento

#include <iostream>

int main() {
  while(1) 
    ;
  std::cout << "Hello" << std::endl;
}

Apparentemente ha qualcosa a che fare con i thread e le capacità di ottimizzazione. Mi sembra che questo possa sorprendere molte persone però.

Qualcuno ha una buona spiegazione del perché questo era necessario per consentire? Per riferimento, la più recente bozza C ++ 0x dice a6.5/5

Un ciclo che, al di fuori dell'istruzione for-init nel caso di un'istruzione for,

  • non effettua chiamate alle funzioni I / O della libreria e
  • non accede o modifica oggetti volatili e
  • non esegue operazioni di sincronizzazione (1.10) o operazioni atomiche (clausola 29)

l'implementazione può essere considerata terminata. [Nota: questo ha lo scopo di consentire le trasformazioni del compilatore, come la rimozione di loop vuoti, anche quando non è possibile provare la terminazione. - nota finale]

Modificare:

Questo approfondito articolo parla del testo degli Standard

Sfortunatamente, le parole "comportamento indefinito" non vengono utilizzate. Tuttavia, ogni volta che lo standard dice "il compilatore può assumere P", è implicito che un programma che ha la proprietà non-P ha una semantica indefinita.

È corretto e al compilatore è permesso stampare "Bye" per il programma sopra?


C'è un thread ancora più penetrante qui , che riguarda un analogo cambiamento in C, iniziato dal Guy fatto l'articolo sopra riportato. Tra gli altri fatti utili, presentano una soluzione che sembra valere anche per C ++ 0x ( Aggiornamento : non funzionerà più con n3225 - vedi sotto!)

endless:
  goto endless;

A un compilatore non è permesso ottimizzarlo, a quanto pare, perché non è un loop, ma un salto. Un altro ragazzo riassume la modifica proposta in C ++ 0x e C201X

Scrivendo un ciclo, il programmatore sta affermando sia che l'anello fa qualcosa con comportamento visibile (esegue I / O, accessi oggetti volatili, o sincronizzazione esegue o operazioni atomiche), o che alla fine termina. Se viola tale presupposto scrivendo un ciclo infinito senza effetti collaterali, sto mentendo al compilatore e il comportamento del mio programma è indefinito. (Se sono fortunato, il compilatore potrebbe avvertirmi al riguardo.) Il linguaggio non fornisce (non fornisce più?) Un modo per esprimere un ciclo infinito senza comportamento visibile.


Aggiornamento del 3.1.2011 con n3225: la commissione sposta il testo all'1.10 / 24 e dice

L'implementazione può presumere che qualsiasi thread alla fine eseguirà una delle seguenti operazioni:

  • terminare,
  • effettuare una chiamata a una funzione I / O della libreria,
  • accedere o modificare un oggetto volatile, o
  • eseguire un'operazione di sincronizzazione o un'operazione atomica.

Il gototrucco non funzionerà più!


4
while(1) { MyMysteriousFunction(); }deve essere compilabile in modo indipendente senza conoscere la definizione di quella misteriosa funzione, giusto? Quindi, come possiamo determinare se effettua chiamate a qualsiasi funzione I / O della libreria? In altre parole: sicuramente quel primo proiettile potrebbe essere formulato non fa chiamate a funzioni .
Daniel Earwicker,

19
@Daniel: se ha accesso alla definizione della funzione, può dimostrare molte cose. Esiste l'ottimizzazione interprocedurale.
Potatoswatter,

3
In questo momento, in C ++ 03, un compilatore può cambiare int x = 1; for(int i = 0; i < 10; ++i) do_something(&i); x++;in for(int i = 0; i < 10; ++i) do_something(&i); int x = 2;? O forse viceversa, con xl'inizializzazione 2prima del ciclo. Può dire do_somethingche non importa del valore di x, quindi è un'ottimizzazione perfettamente sicura, se do_something non fa icambiare il valore in modo da finire in un ciclo infinito.
Dennis Zickefoose,

4
Questo significa che main() { start_daemon_thread(); while(1) { sleep(1000); } }potrebbe uscire immediatamente invece di eseguire il mio demone in un thread in background?
Gabe,

2
"Questo articolo approfondito" presuppone che un comportamento specifico sia un comportamento indefinito semplicemente perché non esiste un comportamento esplicito e definito. È un presupposto errato. In generale, quando lo standard lascia aperto un numero finito di comportamenti, un'implementazione deve scegliere uno di questi ( comportamento non specificato ). Questo non deve essere deterministico. Se un ciclo do-nothing termina è probabilmente una scelta booleana; o lo fa o no. Fare qualcos'altro non è permesso.
MSalters,

Risposte:


33

Qualcuno ha una buona spiegazione del perché questo era necessario per consentire?

Sì, Hans Boehm fornisce una logica per questo in N1528: Perché un comportamento indefinito per loop infiniti? , sebbene si tratti del documento WG14, la logica si applica anche al C ++ e il documento fa riferimento sia al WG14 che al WG21:

Come indica correttamente N1509, l'attuale bozza fornisce essenzialmente un comportamento indefinito a cicli infiniti in 6.8.5p6. Un grosso problema per farlo è che consente al codice di spostarsi attraverso un ciclo potenzialmente non terminante. Ad esempio, supponiamo di avere i seguenti loop, dove count 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;
}

Questi due loop potrebbero essere uniti e sostituiti dal seguente loop?

for (p = q; p != 0; p = p -> next) {
        ++count;
        ++count2;
}

Senza la dispensa speciale in 6.8.5p6 per loop infiniti, questo non sarebbe consentito: se il primo loop non termina perché q punta a un elenco circolare, l'originale non scrive mai su count2. Quindi potrebbe essere eseguito in parallelo con un altro thread che accede o aggiorna count2. Questo non è più sicuro con la versione trasformata che accede a count2 nonostante il ciclo infinito. Pertanto la trasformazione potenzialmente introduce una corsa ai dati.

In casi come questo, è molto improbabile che un compilatore sia in grado di provare la terminazione del loop; dovrebbe capire che q indica un elenco aciclico, che credo sia al di là delle capacità della maggior parte dei compilatori tradizionali e spesso impossibile senza le informazioni complete del programma.

Le restrizioni imposte dai loop non terminanti sono una restrizione all'ottimizzazione dei loop terminanti per i quali il compilatore non può provare la terminazione, nonché all'ottimizzazione dei loop effettivamente non terminanti. I primi sono molto più comuni dei secondi e spesso più interessanti da ottimizzare.

Esistono chiaramente anche for-loop con una variabile di ciclo intero in cui sarebbe difficile per un compilatore provare la terminazione, e quindi sarebbe difficile per il compilatore ristrutturare i loop senza 6.8.5p6. Anche qualcosa del genere

for (i = 1; i != 15; i += 2)

o

for (i = 1; i <= 10; i += j)

sembra non banale da gestire. (Nel primo caso, è necessaria una teoria dei numeri di base per dimostrare la terminazione, nel secondo caso, dobbiamo sapere qualcosa sui possibili valori di j per farlo. L'avvolgimento per interi senza segno può complicare ulteriormente alcuni di questi ragionamenti. )

Questo problema sembra applicarsi a quasi tutte le trasformazioni di ristrutturazione del ciclo, inclusa la parallelizzazione del compilatore e le trasformazioni di ottimizzazione della cache, entrambe le quali probabilmente acquisiranno importanza e sono già spesso importanti per il codice numerico. Ciò sembra probabilmente trasformarsi in un costo sostanziale a vantaggio della possibilità di scrivere loop infiniti nel modo più naturale possibile, soprattutto perché la maggior parte di noi raramente scrive loop intenzionalmente infiniti.

La principale differenza con C è che C11 fornisce un'eccezione per il controllo di espressioni che sono espressioni costanti che differiscono da C ++ e rendono il tuo esempio specifico ben definito in C11.


1
Ci sono delle ottimizzazioni sicure e utili che sono facilitate dal linguaggio attuale che non sarebbero facilitate anche dicendo "Se la fine di un ciclo dipende dallo stato di qualsiasi oggetto, il tempo necessario per eseguire il ciclo non è considerato un effetto collaterale osservabile, anche se tale tempo sembra essere infinito ". Dato il do { x = slowFunctionWithNoSideEffects(x);} while(x != 23);codice di sollevamento dopo il ciclo da cui non dipenderebbe xsembrerebbe sicuro e ragionevole, ma consentire a un compilatore di assumere x==23in tale codice sembra più pericoloso che utile.
supercat,

47

Per me, la giustificazione pertinente è:

Ciò ha lo scopo di consentire le trasformazioni del compilatore, come la rimozione di loop vuoti, anche quando non è possibile provare la terminazione.

Presumibilmente, ciò è dovuto al fatto che provare la terminazione meccanicamente è difficile e l'incapacità di provare la terminazione ostacola i compilatori che potrebbero altrimenti apportare trasformazioni utili, come spostare operazioni non dipendenti da prima del ciclo a dopo o viceversa, eseguendo operazioni post-ciclo in un thread mentre il ciclo viene eseguito in un altro e così via. Senza queste trasformazioni, un loop potrebbe bloccare tutti gli altri thread mentre attendono che un thread termini il loop stesso. (Uso vagamente "thread" per indicare qualsiasi forma di elaborazione parallela, inclusi flussi di istruzioni VLIW separati.)

EDIT: esempio stupido:

while (complicated_condition()) {
    x = complicated_but_externally_invisible_operation(x);
}
complex_io_operation();
cout << "Results:" << endl;
cout << x << endl;

Qui, sarebbe più veloce per un thread fare l'altro complex_io_operationmentre l'altro sta facendo tutti i calcoli complessi nel ciclo. Ma senza la clausola che hai citato, il compilatore deve provare due cose prima di poter effettuare l'ottimizzazione: 1) che complex_io_operation()non dipende dai risultati del ciclo e 2) che il ciclo terminerà . Provare 1) è abbastanza facile, dimostrando 2) è il problema di arresto. Con la clausola, si può presumere che il loop termina e ottenere una vincita di parallelizzazione.

Immagino anche che i progettisti abbiano considerato che i casi in cui si verificano cicli infiniti nel codice di produzione sono molto rari e di solito sono cose come i cicli guidati da eventi che accedono all'I / O in qualche modo. Di conseguenza, hanno pessimizzato il caso raro (loop infiniti) a favore dell'ottimizzazione del caso più comune (loop non infiniti, ma difficili da provare meccanicamente non infiniti).

Ciò significa, tuttavia, che i loop infiniti utilizzati negli esempi di apprendimento ne risentiranno e aumenteranno i gotcha nel codice per principianti. Non posso dire che sia del tutto una buona cosa.

EDIT: rispetto all'articolo approfondito che ora colleghi, direi che "il compilatore può supporre X sul programma" è logicamente equivalente a "se il programma non soddisfa X, il comportamento non è definito". Possiamo mostrarlo come segue: supponiamo che esista un programma che non soddisfa la proprietà X. Dove sarebbe definito il comportamento di questo programma? Lo standard definisce solo il comportamento presupponendo che la proprietà X sia vera. Sebbene lo standard non dichiari esplicitamente indefinito il comportamento, lo ha dichiarato non definito per omissione.

Considera un argomento simile: "il compilatore può presumere che una variabile x sia assegnata al massimo una sola volta tra punti di sequenza" equivale a "assegnare a x più di una volta tra punti sequenza non è definito".


"Provare 1) è piuttosto semplice" - in effetti non segue immediatamente dalle 3 condizioni per consentire al compilatore di assumere la terminazione in loop ai sensi della clausola di cui Johannes sta chiedendo? Penso che equivalgano a "il loop non ha alcun effetto osservabile, tranne forse che gira per sempre", e la clausola assicura che "girare per sempre" non sia un comportamento garantito per tali loop.
Steve Jessop,

@Steve: è facile se il ciclo non termina; ma se il ciclo termina, potrebbe avere un comportamento non banale che influenza l'elaborazione di complex_io_operation.
Philip Potter,

Oops, sì, mi sono perso che potrebbe modificare i locali / alias non volatili / qualunque cosa siano utilizzati nell'IO op. Quindi hai ragione: sebbene non necessariamente segua, ci sono molti casi in cui i compilatori possono e dimostrano che non si verifica tale modifica.
Steve Jessop,

"Tuttavia, ciò significa che i cicli infiniti utilizzati negli esempi di apprendimento ne risentiranno di conseguenza e aumenteranno i gotcha nel codice per principianti. Non posso dire che sia del tutto una buona cosa." Basta compilare con le ottimizzazioni disattivate e dovrebbe funzionare ancora
KitsuneYMG

1
@supercat: Quello che descrivi è ciò che accadrà in pratica, ma non è ciò che richiede il progetto di standard. Non possiamo supporre che il compilatore non sappia se un ciclo terminerà. Se il compilatore fa conoscere il ciclo non terminerà, si può fare quello che vuole. Il DS9K sarà creare demoni nasali per qualsiasi ciclo infinito senza I / O, ecc (quindi, le risolve DS9K il problema della terminazione.)
Philip Potter

15

Penso che l'interpretazione corretta sia quella della tua modifica: i loop infiniti vuoti sono comportamenti indefiniti.

Non direi che è un comportamento particolarmente intuitivo, ma questa interpretazione ha più senso di quella alternativa, secondo cui al compilatore è arbitrariamente permesso di ignorare infiniti loop senza invocare UB.

Se i loop infiniti sono UB, significa solo che i programmi non terminanti non sono considerati significativi: secondo C ++ 0x, non hanno semantica.

Anche questo ha un certo senso. Sono un caso speciale, in cui un certo numero di effetti collaterali non si verificano più (ad esempio, non viene mai restituito nulla main) e un certo numero di ottimizzazioni del compilatore sono ostacolate dalla necessità di conservare loop infiniti. Ad esempio, lo spostamento dei calcoli attraverso il loop è perfettamente valido se il loop non ha effetti collaterali, poiché alla fine il calcolo verrà eseguito in ogni caso. Ma se il ciclo non termina mai, non possiamo riorganizzare in modo sicuro il codice attraverso di esso, perché potremmo semplicemente cambiare quali operazioni vengono effettivamente eseguite prima che il programma si blocchi. A meno che non trattiamo un programma sospeso come UB, cioè.


7
"I loop infiniti vuoti sono comportamenti indefiniti"? Alan Turing avrebbe supplicato di dissentire, ma solo quando si fosse gettato nella sua tomba.
Donal Fellows,

11
@Donal: non ho mai detto nulla della sua semantica in una macchina di Turing. Stiamo discutendo la semantica di un ciclo infinito senza effetti collaterali in C ++ . E mentre lo leggo, C ++ 0x sceglie di dire che tali loop non sono definiti.
jalf

I loop infiniti vuoti sarebbero sciocchi e non ci sarebbe motivo di avere regole speciali per loro. La regola è progettata per gestire loop utili di durata illimitata (si spera non infinita), che calcolano qualcosa che sarà necessario in futuro ma non immediatamente.
supercat

1
Questo significa che C ++ 0x non è adatto per i dispositivi integrati? Quasi tutti i dispositivi integrati sono non terminanti e fanno il loro lavoro in un grande grasso while(1){...}. Usano anche di routine while(1);per invocare un ripristino assistito dal cane da guardia.
vsz

1
@vsz: il primo modulo va bene. I loop infiniti sono perfettamente ben definiti, purché abbiano una sorta di comportamento osservabile. La seconda forma è più complicata, ma posso pensare a due vie molto semplici: (1) un compilatore che prende di mira i dispositivi incorporati potrebbe semplicemente scegliere di definire un comportamento più rigoroso in quel caso, o (2) creare un corpo che chiama una funzione di libreria fittizia . Fintanto che il compilatore non sa cosa fa quella funzione, deve presumere che possa avere qualche effetto collaterale e quindi non può scherzare con il ciclo.
jalf

8

Penso che questo sia in linea con questo tipo di domanda , che fa riferimento a un altro thread . L'ottimizzazione può occasionalmente rimuovere i loop vuoti.


3
Bella domanda Sembra che quel ragazzo abbia avuto esattamente il problema che questo paragrafo consente a quel compilatore di causare. Nella discussione collegata di una delle risposte, è scritto che "Sfortunatamente, le parole" comportamento indefinito "non sono usate. Tuttavia, ogni volta che lo standard dice" il compilatore può assumere P ", è implicito che un programma che ha il la proprietà not-P ha una semantica indefinita. " . Questo mi sorprende. Questo significa che il mio programma di esempio sopra ha un comportamento indefinito e può semplicemente segfault dal nulla?
Johannes Schaub - lett.

@Johannes: il testo "si può presumere" non compare da nessun'altra parte nella bozza che devo consegnare e "si può presumere" compare solo un paio di volte. Anche se ho controllato questo con una funzione di ricerca che non riesce a trovare una corrispondenza tra le interruzioni di riga, quindi potrei essermene persa. Quindi non sono sicuro che la generalizzazione dell'autore sia giustificata dalle prove, ma come matematico devo ammettere la logica dell'argomento, che se il compilatore assume qualcosa di falso, in generale può dedurre qualsiasi cosa ...
Steve Jessop,

... Consentire una contraddizione nel ragionamento del compilatore sul programma induce certamente fortemente UB, dal momento che in particolare consente al compilatore, per qualsiasi X, di dedurre che il programma è equivalente a X. Sicuramente consentire al compilatore di dedurre che è permettendogli di farlo . Concordo anche con l'autore sul fatto che se UB è inteso, dovrebbe essere esplicitamente dichiarato, e se non è inteso, il testo della specifica è errato e dovrebbe essere corretto (forse con l'equivalente del linguaggio delle specifiche di ", il compilatore può sostituire il ciclo con codice che non ha alcun effetto ", non sono sicuro).
Steve Jessop,

@SteveJessop: Cosa penseresti di dire semplicemente che l'esecuzione di qualsiasi pezzo di codice - compresi i loop infiniti - può essere posticipata fino a quando qualcosa che il pezzo di codice ha influenzato un comportamento del programma osservabile, e che ai fini di ciò regola, il tempo necessario per eseguire un pezzo di codice - anche se infinito - non è un "effetto collaterale osservabile". Se un compilatore può dimostrare che un ciclo non può uscire senza una variabile che detiene un determinato valore, si può ritenere che la variabile mantenga quel valore, anche si potrebbe anche dimostrare che il ciclo non può uscire con esso mantenendo quel valore.
supercat

@supercat: come hai affermato questa conclusione, non penso che migliori le cose. Se il loop non si chiude mai in modo dimostrabile per qualsiasi oggetto Xe bit-pattern x, il compilatore può dimostrare che il loop non si chiude senza Xtenere il bit-pattern x. È vagamente vero. Quindi si Xpotrebbe ritenere che mantenga qualsiasi modello di bit, e questo è negativo come UB nel senso che per il male Xe xne causerà rapidamente alcuni. Quindi credo che tu debba essere più preciso nel tuo standardese. È difficile parlare di ciò che accade "alla fine di" un ciclo infinito e dimostrarlo equivalente a un'operazione finita.
Steve Jessop,

8

Il problema rilevante è che al compilatore è consentito riordinare il codice i cui effetti collaterali non sono in conflitto. Il sorprendente ordine di esecuzione potrebbe verificarsi anche se il compilatore generasse un codice macchina non terminante per il ciclo infinito.

Credo che questo sia l'approccio giusto. Le specifiche del linguaggio definiscono i modi per far rispettare l'ordine di esecuzione. Se vuoi un loop infinito che non può essere riordinato, scrivi questo:

volatile int dummy_side_effect;

while (1) {
    dummy_side_effect = 0;
}

printf("Never prints.\n");

2
@ JohannesSchaub-litb: se un ciclo - infinito o no - non legge o scrive alcuna variabile volatile durante l'esecuzione e non chiama alcuna funzione che potrebbe farlo, un compilatore è libero di differire qualsiasi parte del ciclo fino a il primo tentativo di accedere a qualcosa in esso calcolato. Dato unsigned int dummy; while(1){dummy++;} fprintf(stderror,"Hey\r\n"); fprintf(stderror,"Result was %u\r\n",dummy);, il primo fprintfpotrebbe essere eseguito, ma il secondo no (il compilatore potrebbe spostare il calcolo dummytra i due fprintf, ma non oltre quello che stampa il suo valore).
supercat

1

Penso che il problema potrebbe forse essere meglio indicato, in quanto "Se un pezzo di codice successivo non dipende da un pezzo di codice precedente e il pezzo di codice precedente non ha effetti collaterali su nessun'altra parte del sistema, l'output del compilatore può eseguire il pezzo di codice successivo prima, dopo, o mescolato, l'esecuzione del primo, anche se il primo contiene loop, indipendentemente da quando o se il codice precedente sarebbe effettivamente completo . Ad esempio, il compilatore potrebbe riscrivere:

void testfermat (int n)
{
  int a = 1, b = 1, c = 1;
  while (pow (a, n) + pow (b, n)! = pow (c, n))
  {
    se (b> a) a ++; altrimenti if (c> b) {a = 1; b ++}; altrimenti {a = 1; b = 1; c ++};
  }
  printf ("Il risultato è");
  printf ("% d /% d /% d", a, b, c);
}

come

void testfermat (int n)
{
  if (fork_is_first_thread ())
  {
    int a = 1, b = 1, c = 1;
    while (pow (a, n) + pow (b, n)! = pow (c, n))
    {
      se (b> a) a ++; altrimenti if (c> b) {a = 1; b ++}; altrimenti {a = 1; b = 1; c ++};
    }
    signal_other_thread_and_die ();
  }
  else // Secondo thread
  {
    printf ("Il risultato è");
    wait_for_other_thread ();
  }
  printf ("% d /% d /% d", a, b, c);
}

Generalmente non irragionevole, anche se potrei preoccuparmi che:

  int total = 0;
  per (i = 0; num_reps> i; i ++)
  {
    update_progress_bar (i);
    totale + = do_something_slow_with_no_side_effects (i);
  }
  show_result (totale);

potrebbe diventare

  int total = 0;
  if (fork_is_first_thread ())
  {
    per (i = 0; num_reps> i; i ++)
      totale + = do_something_slow_with_no_side_effects (i);
    signal_other_thread_and_die ();
  }
  altro
  {
    per (i = 0; num_reps> i; i ++)
      update_progress_bar (i);
    wait_for_other_thread ();
  }
  show_result (totale);

Avendo una CPU che gestisce i calcoli e un'altra che gestisce gli aggiornamenti della barra di avanzamento, la riscrittura migliorerebbe l'efficienza. Sfortunatamente, gli aggiornamenti della barra di avanzamento sarebbero meno utili di quanto dovrebbero essere.


Penso che il caso della barra di avanzamento non possa essere separato, poiché la visualizzazione di una barra di avanzamento è una chiamata I / O della libreria. Le ottimizzazioni non dovrebbero cambiare il comportamento visibile in questo modo.
Philip Potter,

@Philip Potter: se la routine lenta avesse effetti collaterali, sarebbe certamente vero. Nel mio esempio prima, non avrebbe senso se non lo facesse, quindi l'ho cambiato. La mia interpretazione della specifica è che al sistema è permesso di differire l'esecuzione del codice lento fino a quando i suoi effetti (oltre al tempo necessario per l'esecuzione) diventeranno visibili, cioè la chiamata show_result (). Se il codice della barra di avanzamento utilizzava il totale parziale, o almeno pretendeva di farlo, ciò lo costringerebbe a sincronizzarsi con il codice lento.
Supercat,

1
Questo spiega tutte quelle barre di avanzamento che vanno veloci da 0 a 100 e poi si
bloccano

0

Non è decidibile per il compilatore per casi non banali se si tratta di un loop infinito.

In diversi casi, può capitare che il tuo ottimizzatore raggiunga una classe di complessità migliore per il tuo codice (es. Era O (n ^ 2) e ottieni O (n) o O (1) dopo l'ottimizzazione).

Quindi, includere una regola del genere che non consente di rimuovere un ciclo infinito nello standard C ++ renderebbe impossibili molte ottimizzazioni. E molte persone non lo vogliono. Penso che questo risponda abbastanza alla tua domanda.


Un'altra cosa: non ho mai visto alcun esempio valido in cui hai bisogno di un ciclo infinito che non fa nulla.

L'unico esempio di cui ho sentito parlare è stato un brutto hack che avrebbe dovuto essere risolto altrimenti: si trattava di sistemi embedded in cui l'unico modo per attivare un ripristino era congelare il dispositivo in modo che il watchdog lo riavviesse automaticamente.

Se conosci qualche esempio valido / valido in cui hai bisogno di un ciclo infinito che non fa nulla, per favore dimmelo.


1
Esempio di dove potresti desiderare un loop infinito: un sistema incorporato in cui non vuoi dormire per motivi di prestazioni e tutto il codice è sospeso da un interrupt o due?
JCx,

@JCx nella norma C gli interrupt dovrebbero impostare un flag che controlla il loop principale, quindi il loop principale avrebbe un comportamento osservabile nel caso in cui i flag vengano impostati. L'esecuzione di codice sostanziale negli interrupt non è portatile.
MM

-1

Penso che valga la pena sottolineare che i loop che sarebbero infiniti, tranne per il fatto che interagiscono con altri thread tramite variabili non volatili e non sincronizzate, possono ora produrre un comportamento errato con un nuovo compilatore.

In altre parole, rendo volatili i tuoi globi - così come gli argomenti passati in un tale ciclo tramite puntatore / riferimento.


Se interagiscono con altri thread, non dovresti renderli volatili, renderli atomici o proteggerli con un lucchetto.
BCoates

1
Thjs è un consiglio terribile. Rendendoli volatileè né necessario né suffiicent, e fa male notevolmente le prestazioni.
David Schwartz,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.