Bug di compilazione C ++?

Ho il codice seguente:

#include  #include  using namespace std; int main() { complex delta; complex mc[4] = {0}; for(int di = 0; di < 4; di++, delta = mc[di]) { cout << di << endl; } return 0; } 

Mi aspetto che emetta “0, 1, 2, 3” e si fermi, ma emette una serie infinita di “0, 1, 2, 3, 4, 5, …..”

Sembra che il confronto di<4 non funzioni bene e restituisca sempre true.

Se commento ,delta=mc[di] , ottengo “0, 1, 2, 3” normalmente. Qual è il problema con l’assegnazione innocente?

Sto usando Ideone.com g ++ C ++ 14 con l’opzione -O2.

Ciò è dovuto a un comportamento indefinito, si sta accedendo alla matrice mc fuori limite nell’ultima iterazione del ciclo. Alcuni compilatori possono eseguire un ciclo di ottimizzazione aggressivo attorno alle ipotesi di un comportamento non definito. La logica sarebbe simile alla seguente:

  • L’accesso a mc fuori dai limiti è un comportamento indefinito
  • Non assumere comportamenti non definiti
  • Quindi di < 4 è sempre vero dato che altrimenti mc[di] invocerebbe un comportamento indefinito

gcc con ottimizzazione triggersta e l'uso del -fno-aggressive-loop-optimizations fa sì che il comportamento del ciclo infinito scompaia ( guardalo dal vivo ). Mentre un esempio live con ottimizzazione ma senza ottimizzazioni -fno-aggressive-loop mostra il comportamento del ciclo infinito che si osserva.

Un esempio di codice reale del codice mostra che il controllo di < 4 viene rimosso e sostituito con e jmp incondizionato:

 jmp .L6 

Questo è quasi identico al caso delineato nei benchmark GCC pre-4.8 Breaks Broken SPEC 2006 . I commenti a questo articolo sono eccellenti e meritano la lettura. Nota che il clang ha colto il caso nell'articolo usando -fsanitize=undefined che non posso riprodurre per questo caso ma gcc usando -fsanitize=undefined fa ( -fsanitize=undefined dal vivo ). Probabilmente il bug più infame attorno a un ottimizzatore che fa un'inferenza attorno a un comportamento indefinito è la rimozione del controllo del puntatore nullo del kernel Linux .

Sebbene si tratti di ottimizzazioni aggressive, è importante notare che, come lo standard C ++, il comportamento non definito è:

comportamento per il quale questo standard internazionale non impone requisiti

Il che significa essenzialmente che tutto è ansible e rileva ( sottolineatura mia ):

[...] Il comportamento indefinito ammissibile va dall'ignorare completamente la situazione con risultati imprevedibili , a comportarsi durante la traduzione o l'esecuzione del programma in un modo documentato caratteristico dell'ambiente (con o senza emissione di un messaggio diagnostico), a terminare una traduzione o esecuzione (con l'emissione di un messaggio diagnostico). [...]

Per ottenere un avviso da gcc, dobbiamo spostare il cout all'esterno del ciclo e quindi vediamo il seguente avviso ( vederlo dal vivo ):

 warning: iteration 3u invokes undefined behavior [-Waggressive-loop-optimizations] for(di=0; di<4;di++,delta=mc[di]){ } ^ 

che sarebbe stato probabilmente sufficiente a fornire al PO sufficienti informazioni per capire cosa stava succedendo. Incoerenza come questa è tipica dei tipi di comportamento che possiamo vedere con un comportamento indefinito. Per comprendere meglio perché tali distorsioni possono essere inconsistenti di fronte a comportamenti non definiti. Perché non si può avvisare quando si ottimizza in base a comportamenti non definiti? è una buona lettura.

Nota: -fno-aggressive-loop-optimizations è documentato nelle note di rilascio di gcc 4.8 .

Dato che stai incrementando di prima che tu lo usi per indicizzare mc , la quarta volta attraverso il ciclo farai riferimento a mc [4], che è oltre la fine del tuo array, il che potrebbe a sua volta portare a comportamenti fastidiosi.

Tu hai questo:

 for(int di=0; di<4; di++, delta=mc[di]) { cout< 

Prova questo invece:

 for(int di=0; di<4; delta=mc[di++]) { cout< 

MODIFICARE:

Per chiarire cosa sta succedendo, analizziamo il ciclo di iterazione del tuo ciclo:

1a iterazione: Inizialmente di è impostato a 0. Controllo comparativo: è inferiore a 4? Sì, va bene. Incremento di per 1. Ora di = 1. Prendi l'elemento "ennesimo" di mc [] e impostalo come delta. Questa volta stiamo prendendo il 2 ° elemento poiché questo valore indicizzato è 1 e non 0. Infine, esegui il / i blocco / i di codice all'interno del ciclo for.

Seconda iterazione: Ora di è impostato su 1. Controllo comparativo: è inferiore a 4? Sì e procedere. Incremento di per 1. Ora di = 2. Prendi l'elemento "ennesimo" di mc [] e impostalo come delta. Questa volta stiamo prendendo il terzo elemento dal momento che questo valore indicizzato è 2. Infine, esegui il / i blocco / i di codice all'interno del ciclo for.

Terza iterazione: Ora di è impostato su 2. Controllo comparativo: è inferiore a 4? Sì e procedere. Incremento di per 1. Ora di = 3. Prendi l'elemento "ennesimo" di mc [] e impostalo come delta. Questa volta stiamo prendendo il 4 ° elemento dal momento che questo valore indicizzato è 3. Infine esegui il / i blocco / i di codice all'interno del ciclo for.

4a iterazione: Ora di è impostato su 3. Controllo comparativo: è inferiore a 4? Sì e procedere. Incrementa di per 1. Ora di = 4. (Puoi vedere dove sta andando?) Prendi l'elemento "ennesimo" di mc [] e impostalo come delta. Questa volta stiamo prendendo il quinto elemento dal momento che questo valore indicizzato è 4. Uh Oh abbiamo un problema; la nostra dimensione dell'array è solo 4. Delta ha ora spazzatura e questo è un comportamento indefinito o corruzione. Infine, esegui il / i blocco / i di codice all'interno del ciclo for usando "spazzatura delta".

Quinta iterazione. Ora di è impostato su 4. Controllo comparativo: è inferiore a 4? No, esci dal ciclo.

Corruzione superando i limiti della memoria contigua (array).

È perché di ++ viene eseguito nell’ultima esecuzione del ciclo.

Per esempio;

 int di = 0; for(; di < 4; di++); // after the loop di == 4 // (inside the loop we see 0,1,2,3) // (inside the for statement, after di++, we see 1,2,3,4) 

Stai accedendo a mc [] quando di == 4, quindi è un problema fuori dai limiti, potenzialmente distruggendo parte dello stack e corrompendo la variabile di.

una soluzione sarebbe:

 for(int di = 0; di < 4; di++) { cout << di << endl; delta = mc[di]; }