Come faccio a sapere se il compilatore ha rotto il mio codice e cosa devo fare se fosse il compilatore?


14

Di tanto in tanto il codice C ++ non funzionerà se compilato con un certo livello di ottimizzazione. Può essere il compilatore che esegue l'ottimizzazione che rompe il codice o può essere un codice che contiene un comportamento indefinito che consente al compilatore di fare qualunque cosa senta.

Supponiamo che io abbia qualche pezzo di codice che si rompe quando compilato solo con un livello di ottimizzazione più elevato. Come faccio a sapere se si tratta del codice o del compilatore e cosa devo fare se è il compilatore?


43
Molto probabilmente sei tu.
Littleadv,

9
@littleadv, anche le versioni recenti di gcc e msvc sono piene di bug, quindi non ne sarei così sicuro.
SK-logic,

3
Hai tutti gli avvisi abilitati?

@ Thorbjørn Ravn Andersen: Sì, li ho abilitati.
sharptooth,

3
FWIW: 1) Cerco di non fare nulla di complicato che possa indurre il compilatore a sbagliare, 2) l'unico posto in cui i flag di ottimizzazione contano (per la velocità) è nel codice in cui il contatore del programma trascorre una frazione significativa del suo tempo. A meno che tu non stia scrivendo stretti cicli di CPU, in molte applicazioni il PC trascorre essenzialmente tutto il suo tempo in profondità nelle librerie o nell'I / O. In quel tipo di app, le opzioni / O non ti aiutano affatto.
Mike Dunlavey,

Risposte:


19

Direi che è una scommessa sicura che, nella stragrande maggioranza dei casi, è il tuo codice, non il compilatore, che è rotto. E anche nel caso straordinario in cui è il compilatore, probabilmente stai usando alcune funzionalità del linguaggio oscuro in un modo insolito, per il quale il compilatore specifico non è preparato; in altre parole, molto probabilmente potresti cambiare il tuo codice per renderlo più idiomatico ed evitare il punto debole del compilatore.

In ogni caso, se puoi provare di aver trovato un bug del compilatore (basato sulle specifiche della lingua), segnalalo agli sviluppatori del compilatore, in modo che possano risolverlo un po 'di tempo.


@ SK-logic, abbastanza giusto, non ho statistiche per sostenerlo. Si basa sulla mia esperienza e ammetto di aver raramente allungato i limiti del linguaggio e / o del compilatore - altri potrebbero farlo più spesso.
Péter Török,

(1) @ SK-Logic: ho appena trovato un bug del compilatore C ++, stesso codice, provato su un compilatore e funziona, provato in un altro si è rotto.
Umlcat,

8
@umlcat: molto probabilmente era il tuo codice a seconda del comportamento non specificato; su un compilatore soddisfa le tue aspettative, su un altro no. ciò non significa che sia rotto.
Javier,

@Ritch Melton, hai mai usato LTO?
SK-logic,

1
Sono d'accordo con Crashworks, quando si parla di console di gioco. Non è affatto raro trovare bug esoterici del compilatore in quella situazione specifica. Se stai prendendo di mira i PC normali, tuttavia, utilizzando un compilatore molto utilizzato, è molto improbabile che ti imbatterai in un bug del compilatore che nessuno ha mai visto prima.
Trevor Powell,

14

Come al solito, come con qualsiasi altro bug: esegui un esperimento controllato. Restringi l'area sospetta, disattiva le ottimizzazioni per tutto il resto e inizia a variare le ottimizzazioni applicate a quel blocco di codice. Una volta ottenuta una riproducibilità al 100%, inizia a variare il codice, introducendo elementi che potrebbero interrompere determinate ottimizzazioni (ad esempio, introdurre un possibile alias puntatore, inserire chiamate esterne con potenziali effetti collaterali, ecc.). Anche guardare il codice assembly in un debugger potrebbe essere d'aiuto.


potrebbe aiutare con cosa? Se si tratta di un bug del compilatore, e allora?
Littleadv,

2
@littleadv, se si tratta di un bug del compilatore, puoi provare a risolverlo (o semplicemente segnalarlo correttamente, in dettaglio), oppure puoi scoprire come evitarlo in futuro, se sei destinato a continuare a usarlo versione del compilatore per un po '. Se si tratta di qualcosa con il tuo codice, uno dei numerosi problemi borderline di C ++, questo tipo di controllo aiuta anche a correggere un bug ed evitare il suo tipo in futuro.
Logica SK

Quindi, come ho detto nella mia risposta, oltre a riferire, non c'è molta differenza nel trattamento, indipendentemente da chi sia la colpa.
Littleadv,

3
@littleadv, senza capire la natura di un problema è probabile che lo si debba affrontare ancora e ancora. E c'è spesso la possibilità di riparare un compilatore da solo. E, sì, sfortunatamente non è affatto "improbabile" trovare un bug in un compilatore C ++.
SK-logic,

10

Esamina il codice assembly risultante e verifica se fa ciò che richiede la tua fonte. Ricorda che le probabilità sono molto alte che è davvero il tuo codice in difetto in un modo non ovvio.


1
Questa è davvero l'unica risposta a questa domanda. Il lavoro dei compilatori, in questo caso, è di passare da C ++ al linguaggio assembly. Pensi che sia il compilatore ... controlla che i compilatori funzionino. È così semplice
old_timer

7

In oltre 30 anni di programmazione, il numero di bug del compilatore (generazione di codice) originali che ho trovato è ancora solo ~ 10. Il numero dei miei bug (e di altre persone) che ho trovato e corretto nello stesso periodo è probabilmente > 10.000. La mia "regola empirica" ​​è quindi che la probabilità che un determinato bug sia dovuto al compilatore è <0,001.


1
Sei fortunato. La mia media è di circa 1 bug davvero brutto al mese, e piccoli problemi al limite sono molto più frequenti. E maggiore è il livello di ottimizzazione che stai utilizzando, maggiori sono le possibilità di errore del compilatore. Se stai cercando di usare -O3 e LTO, saresti molto fortunato a non trovarne un paio in pochissimo tempo. E qui conto solo i bug nelle versioni di rilascio - come sviluppatore di compilatori, sto affrontando molti più problemi di quel tipo nel mio lavoro, ma questo non conta. So solo quanto sia facile rovinare un compilatore.
SK-logic,

2
25 anni e ho visto anche molti. I compilatori stanno peggiorando ogni anno.
old_timer

5

Ho iniziato a scrivere un commento e poi ho deciso che era troppo lungo e troppo al punto.

Direi che è il tuo codice che è rotto. Nel caso improbabile che tu abbia scoperto un bug nel compilatore, dovresti segnalarlo agli sviluppatori del compilatore, ma è qui che finisce la differenza.

La soluzione è identificare il costrutto offensivo e riformattarlo in modo che farebbe la stessa logica in modo diverso. Molto probabilmente questo risolverebbe il problema, indipendentemente dal fatto che il bug sia dalla tua parte o nel compilatore.


5
  1. Rileggi il tuo codice accuratamente. Assicurati di non fare cose con effetti collaterali in ASSERT o altre istruzioni specifiche di debug (o più generali, di configurazione). Ricorda inoltre che in una build di debug la memoria viene inizializzata in modo diverso - i valori del puntatore rivelatore possono essere controllati qui: Debug - Rappresentazioni di allocazione della memoria . Quando si esegue da Visual Studio, si utilizza quasi sempre l'heap di debug (anche in modalità di rilascio) a meno che non si specifichi esplicitamente con una variabile di ambiente che non è ciò che si desidera.
  2. Controlla la tua build. È comune avere problemi con build complesse in altri luoghi rispetto al compilatore reale - le dipendenze sono spesso il colpevole. So che "hai provato a ricostruire completamente" è una risposta quasi esasperante quanto "hai provato a reinstallare Windows", ma spesso aiuta. Prova: a) Riavvio. b) Eliminazione manuale di tutti i file intermedi e di output e ricostruzione.
  3. Esamina il codice per verificare eventuali posizioni potenziali in cui potresti invocare comportamenti indefiniti. Se lavori in C ++ da un po ', saprai che ci sono alcuni punti in cui pensi "Non sono INTERAMENTE sicuro che mi sia permesso di supporre ..." - google o chiedi qui su quel particolare tipo di codice per vedere se si tratta di un comportamento indefinito o meno.
  4. Se il problema persiste, genera un output preelaborato per il file che causa i problemi. Un'espansione macro inattesa può causare ogni tipo di divertimento (mi viene in mente quando un collega ha deciso che una macro con il nome di H sarebbe una buona idea ...). Esaminare l'output preelaborato per modifiche impreviste tra le configurazioni del progetto.
  5. Ultima risorsa - ora sei davvero nella terra dei bug del compilatore - guarda l'output dell'assembly. Questo potrebbe richiedere alcuni scavi e combattimenti solo per capire cosa sta facendo l'assemblea, ma in realtà è piuttosto informativo. Puoi utilizzare le competenze acquisite qui per valutare anche le microottimizzazioni, quindi non tutto è perduto.

+1 per "comportamento indefinito". Sono stato morso da quello. Ha scritto del codice che dipendeva int + intdall'overflow esattamente come se fosse stato compilato in un'istruzione ADD hardware. Funzionava perfettamente quando compilato con una versione precedente di GCC, ma non quando compilato con il compilatore più recente. Apparentemente le brave persone di GCC hanno deciso che, poiché il risultato di un overflow di numeri interi è indefinito, il loro ottimizzatore potrebbe funzionare supponendo che non accada mai. Ha ottimizzato un ramo importante fin dal codice.
Solomon Slow,

2

Se vuoi sapere se è il tuo codice o il compilatore, devi conoscere perfettamente le specifiche del C ++.

Se il dubbio persiste, devi conoscere perfettamente l'assemblaggio x86.

Se non hai voglia di imparare entrambi alla perfezione, allora è quasi certamente un comportamento indefinito che il tuo compilatore si risolve in modo diverso a seconda del livello di ottimizzazione.


(+1) @mouviciel: dipende anche se la funzionalità è supportata dal compilatore, anche se è nelle specifiche. Ho un bug strano con gcc. Dichiaro una "struttura semplice in c" con un "puntatore a funzione", è consentito nelle specifiche, ma funziona in alcune situazioni e non in un'altra.
Umlcat,

1

Ottenere un errore di compilazione sul codice standard o un errore di compilazione interno è più probabile che gli ottimizzatori stiano sbagliando. Ma ho sentito parlare di compilatori che ottimizzano i loop dimenticando erroneamente alcuni effetti collaterali causati da un metodo.

Non ho suggerimenti su come sapere se sei tu o il compilatore. Puoi provare un altro compilatore.

Un giorno mi chiedevo se fosse il mio codice o meno e qualcuno mi ha suggerito di valgrind. Ho impiegato i 5 o 10 minuti per eseguire il mio programma (penso di valgrind --leak-check=yes myprog arg1 arg2averlo fatto ma ho giocato con altre opzioni) e mi ha mostrato immediatamente UNA riga che è stata eseguita in un caso specifico che era il problema. Quindi la mia app ha funzionato senza problemi da allora senza strani incidenti, errori o comportamenti strani. valgrind o un altro strumento come questo è un buon modo per sapere se è il tuo codice.

Nota a margine: una volta mi chiedevo perché le prestazioni della mia app facessero schifo. Si è scoperto che anche tutti i miei problemi di prestazioni erano in una riga. Ho scritto for(int i=0; i<strlen(sz); ++i) {. La sz era di pochi mb. Per qualche motivo, il compilatore veniva eseguito ogni volta anche dopo l'ottimizzazione. Una riga può essere un grosso problema. Dagli spettacoli agli incidenti


1

Una situazione sempre più comune è che i compilatori rompono il codice scritto per i dialetti di C che supporta comportamenti non obbligati dalla norma e consente al codice che si rivolge a quei dialetti di essere più efficiente di quanto potrebbe essere il codice strettamente conforme. In tal caso, sarebbe ingiusto descrivere come codice "rotto" che sarebbe affidabile al 100% sui compilatori che implementavano il dialetto di destinazione, o descrivere come "rotto" il compilatore che elabora un dialetto che non supporta la semantica richiesta . Invece, i problemi derivano semplicemente dal fatto che il linguaggio elaborato dai compilatori moderni con le ottimizzazioni abilitate si discosta dai dialetti che erano popolari (e sono ancora elaborati da molti compilatori con le ottimizzazioni disabilitate o da alcuni anche con le ottimizzazioni abilitate).

Ad esempio, viene scritto molto codice per i dialetti che riconoscono come legittimi un certo numero di pattern di aliasing puntatore non obbligati dall'interpretazione di gcc dello Standard e fanno uso di tali pattern per consentire una traduzione semplice del codice per essere più leggibile ed efficiente di quanto sarebbe possibile nell'interpretazione di gcc dello standard C. Tale codice potrebbe non essere compatibile con gcc, ma ciò non implica che sia rotto. Si basa semplicemente su estensioni che gcc supporta solo con ottimizzazioni disabilitate.


Bene, certo che non c'è nulla di sbagliato nella codifica delle estensioni X e Y standard Z +, purché ciò ti dia vantaggi significativi, sai di averlo fatto e di averlo documentato a fondo. Purtroppo, tutte e tre le condizioni normalmente non sono soddisfatte , quindi è giusto dire che il codice è rotto.
Deduplicatore,

@Deduplicator: i compilatori C89 sono stati promossi come compatibili con le versioni precedenti con i loro predecessori, e allo stesso modo per C99, ecc. Mentre C89 non impone requisiti sui comportamenti che erano stati precedentemente definiti su alcune piattaforme ma non su altri, la compatibilità verso l'alto suggerirebbe che i compilatori C89 per le piattaforme che avevano considerato comportamenti definiti dovrebbero continuare a farlo; la logica per la promozione di tipi non firmati corti ai segni suggerirebbe che gli autori dello standard si aspettassero che i compilatori si comportassero in quel modo indipendentemente dal fatto che lo standard lo imponesse o meno. Inoltre ...
supercat,

... un'interpretazione rigorosa delle regole di aliasing getterebbe fuori dalla finestra la compatibilità verso l'alto e renderebbe inattuabili molti tipi di codice, ma alcune lievi modifiche (ad esempio identificando alcuni schemi in cui dovrebbe essere previsto un aliasing di tipo incrociato e quindi ammissibile) risolverebbero entrambi i problemi . Lo scopo dichiarato della regola era quello di evitare di richiedere ai compilatori di fare ipotesi di aliasing "pessimistico", ma dato "float x", se si presumesse che "foo ((int *) & x)" potrebbe modificare x anche se "foo" non lo fa scrivi a qualsiasi puntatore di tipo 'float * "o" char * "essere considerato" pessimista "o" ovvio "?
supercat

0

Isolare il punto problematico e confrontare il comportamento osservato con ciò che dovrebbe accadere in base alle specifiche della lingua. Sicuramente non è facile, ma è quello che devi fare per sapere (e non solo supporre ).

Probabilmente non sarei così meticoloso. Piuttosto, chiederei al forum di supporto / alla mailing list del produttore del compilatore. Se è davvero un bug nel compilatore, potrebbero risolverlo. Probabilmente sarebbe comunque il mio codice. Ad esempio, le specifiche del linguaggio relative alla visibilità della memoria nel threading possono essere abbastanza controintuitive e potrebbero diventare evidenti solo quando si utilizzano alcuni flag di ottimizzazione specifici, su un hardware specifico (!). Alcuni comportamenti potrebbero non essere definiti dalle specifiche, quindi potrebbero funzionare con alcuni compilatori / alcuni flag e non funzionare con altri, ecc.


0

Molto probabilmente il tuo codice ha un comportamento indefinito (come spiegato da altri, è molto più probabile che tu abbia dei bug nel tuo codice rispetto al compilatore, anche se i compilatori C ++ sono così complessi da avere bug; anche la specifica C ++ ha bug di progettazione) . E UB può essere qui anche se l'eseguibile compilato sembra funzionare (per sfortuna).

Quindi dovresti leggere il blog di Lattner Quello che ogni programmatore C dovrebbe sapere sul comportamento indefinito (la maggior parte si applica anche a C ++ 11).

Anche lo strumento valgrind e le recenti -fsanitize= opzioni di strumentazione per GCC (o Clang / LLVM ) dovrebbero essere utili. E, naturalmente, abilitare tutti gli avvisi:g++ -Wall -Wextra

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.