it-swarm.it

Come mai i compilatori sono così affidabili?

Usiamo i compilatori su base giornaliera come se la loro correttezza sia data, ma anche i compilatori sono programmi e possono potenzialmente contenere bug. Mi sono sempre chiesto di questa infallibile robustezza. Hai mai riscontrato un bug nel compilatore stesso? Cos'è stato e come hai capito che il problema era nel compilatore stesso?

... e come do rendono i compilatori così affidabili?

67
EpsilonVector

Vengono testati accuratamente tramite l'utilizzo da parte di migliaia o addirittura milioni di sviluppatori nel tempo.

Inoltre, il problema da risolvere è ben definito (da una specifica tecnica molto dettagliata). E la natura dell'attività si presta facilmente ai test unitari/di sistema. Cioè fondamentalmente sta traducendo l'input testuale in un formato molto specifico per l'output in un altro tipo di formato ben definito (una sorta di bytecode o codice macchina). Quindi è facile creare e verificare casi di test.

Inoltre, di solito anche i bug sono facili da riprodurre: a parte le informazioni esatte sulla piattaforma e sulla versione del compilatore, di solito tutto ciò che serve è un pezzo di codice di input. Per non parlare del fatto che gli utenti del compilatore (essendo essi stessi sviluppatori) tendono a fornire segnalazioni di bug molto più precise e dettagliate rispetto a qualsiasi utente medio di computer :-)

101
Péter Török

Oltre a tutte le grandi risposte finora:

Hai un "pregiudizio dell'osservatore". Non osservi i bug e quindi presumi che non ce ne siano.

Pensavo come te. Poi ho iniziato a scrivere compilatori in modo professionale, e lascia che te lo dica, ci sono molti bug lì dentro!

Non vedi i bug perché scrivi codice che è proprio come il 99,999% di tutto il resto del codice che la gente scrive. Probabilmente scrivi un codice perfettamente normale, semplice e chiaramente corretto che chiama metodi ed esegue cicli e non fa nulla di strano o di strano, perché sei uno sviluppatore normale che risolve i normali problemi aziendali.

Non vedi alcun bug del compilatore perché i bug del compilatore non sono negli scenari di codice normale semplici da analizzare; i bug sono nell'analisi di codice strano che non si scrive.

Io d'altra parte ho il pregiudizio opposto dell'osservatore. Vedo codice pazzo tutto il giorno ogni giorno, e quindi per me i compilatori sembrano essere pieni di bug.

Se ti sei seduto con le specifiche del linguaggio di qualsiasi lingua e hai preso l'implementazione di un compilatore per quella lingua, e hai davvero cercato di determinare se il compilatore ha implementato esattamente le specifiche o meno, concentrandoti su casi angolari oscuri, molto presto potresti trovare bug del compilatore abbastanza frequentemente. Lascia che ti faccia un esempio, ecco un bug del compilatore C # che ho trovato letteralmente cinque minuti fa.

static void N(ref int x){}
...
N(ref 123);

Il compilatore fornisce tre errori.

  • Un argomento ref o out deve essere una variabile assegnabile.
  • La migliore corrispondenza per N (ref int x) ha argomenti non validi.
  • "Ref" mancante sull'argomento 1.

Ovviamente il primo messaggio di errore è corretto e il terzo è un bug. L'algoritmo di generazione dell'errore sta cercando di capire perché il primo argomento non è valido, lo osserva, vede che è una costante e non torna al codice sorgente per verificare se è stato contrassegnato come "ref"; piuttosto, presume che nessuno sarebbe abbastanza sciocco da contrassegnare una costante come ref e decide che l'arbitro debba mancare.

Non è chiaro quale sia il terzo messaggio di errore corretto, ma non è così. In effetti, non è chiaro se il messaggio di errore secondo sia corretto. La risoluzione del sovraccarico dovrebbe fallire o "ref 123" dovrebbe essere trattato come un argomento ref del tipo corretto? Ora dovrò pensarci e parlarne con il team di triage in modo che possiamo determinare quale sia il comportamento corretto.

Non hai mai visto questo bug perché probabilmente non avresti mai fatto qualcosa di così sciocco da provare a passare 123 con ref. E se lo facessi, probabilmente non noteresti nemmeno che il terzo messaggio di errore è privo di senso, poiché il primo è corretto e sufficiente per diagnosticare il problema. Ma provo a fare cose del genere, perché sto cercando per interrompere il compilatore. Se ci provassi, vedresti anche i bug.

66
Eric Lippert

Ma stai scherzando? Anche i compilatori hanno bug, carichi davvero.

GCC è probabilmente il più celebre compilatore open source del pianeta e dai un'occhiata al suo database di bug: http://gcc.gnu.org/bugzilla/buglist.cgi?product=gcc&component=c%2B% 2B e risoluzione = ---

Tra GCC 3.2 e GCC 3.2.3 dai un'occhiata a quanti bug sono stati corretti: http://gcc.gnu.org/gcc-3.2/changes.html

Per quanto riguarda altri come Visual C++, non voglio nemmeno iniziare.

Come si rendono affidabili i compilatori? Bene, per cominciare hanno un sacco di test unitari. E l'intero pianeta li usa, quindi nessuna mancanza di tester.

Scherzi a parte, gli sviluppatori di compilatori che mi piace credere siano programmatori di livello superiore e, sebbene non siano infallibili, hanno un bel pugno.

54
Fanatic23

Ne ho incontrati due o tre ai miei tempi. L'unico vero modo per rilevarne uno è guardare il codice dell'Assemblea.

Sebbene i compilatori siano altamente affidabili per i motivi segnalati da altri poster, penso che l'affidabilità del compilatore sia spesso una valutazione che si autoavvera. I programmatori tendono a vedere il compilatore come standard. Quando qualcosa va storto, si presume che sia colpa tua (perché il 99,999% delle volte lo è) e si modifica il codice per aggirare il problema del compilatore anziché viceversa. Ad esempio, l'arresto anomalo del codice con un'impostazione di ottimizzazione elevata è sicuramente un bug del compilatore, ma la maggior parte delle persone lo imposta solo un po 'più in basso e continua senza segnalare il bug.

21
Karl Bielefeldt

I compilatori hanno diverse proprietà che portano alla loro correttezza:

  • Il dominio è molto conosciuto e studiato. Il problema è ben definito e le soluzioni offerte sono ben definite.
  • Il test automatico è sufficiente per dimostrare che i compilatori funzionano correttamente
  • I compilatori hanno test molto ampi, in genere pubblici, automatizzati e unitari, che si sono accumulati nel tempo per coprire più spazio di errore rispetto alla maggior parte degli altri programmi
  • I compilatori hanno un gran numero di bulbi oculari che guardano i loro risultati
15
blueberryfields

Usiamo compilatori su base giornaliera

... e come rendono i compilatori così affidabili?

Non lo fanno. Noi facciamo. Perché tutti li usano sempre, i bug vengono trovati rapidamente.

È un gioco di numeri. Poiché i compilatori vengono utilizzati in modo così pervasivo, è molto probabile che qualsiasi bug sarà venga attivato da qualcuno, ma poiché c'è un numero così elevato di utenti, è altamente improbabile che qualcuno sarà te in particolare.

Quindi, dipende dal tuo punto di vista: tra tutti gli utenti, i compilatori sono buggy. Ma è molto probabile che qualcun altro abbia compilato un pezzo di codice simile prima di te, quindi se il loro era un bug, li avrebbe colpiti, non tu, quindi dal tuo individuale punto di vista, sembra che il bug non sia mai stato lì.

Naturalmente, oltre a ciò, puoi aggiungere tutte le altre risposte qui: i compilatori sono ben studiati, ben compresi. C'è questo mito che sono difficili da scrivere, il che significa che solo programmatori molto intelligenti e molto bravi in ​​realtà tentano di scriverne uno e sono molto attenti quando lo fanno. Sono generalmente facili da testare e facili da stress test o fuzz test. Gli utenti del compilatore tendono ad essere programmatori esperti stessi, portando a segnalazioni di bug di alta qualità. E viceversa: gli autori di compilatori tendono ad essere utenti del proprio compilatore.

14
Jörg W Mittag

Oltre a tutte le risposte, vorrei aggiungere:

Io credo molte volte, i venditori stanno mangiando il proprio cibo per cani. Cioè, stanno scrivendo i compilatori in sé.

12
DevSolo

Mi sono imbattuto spesso in bug del compilatore.

Li puoi trovare negli angoli più bui dove ci sono meno tester. Ad esempio, per trovare bug in GCC dovresti provare:

  • Crea un cross-compilatore. Troverai letteralmente dozzine di bug negli script di configurazione e compilazione di GCC. Alcuni si traducono in errori di compilazione durante la compilazione di GCC e altri in errori di compilazione incrociata nella creazione di eseguibili funzionanti.
  • Costruisci una versione Itanium di GCC usando profile-bootstrap. L'ultima volta che l'ho provato su GCC 4.4 e 4.5 non è riuscito a produrre un gestore di eccezioni C++ funzionante. La build non ottimizzata ha funzionato bene. Nessuno sembrava interessato a correggere il bug che ho segnalato e ho rinunciato a risolverlo da solo dopo aver cercato di scavare attraverso ciò che stava rompendo le specifiche della memoria asm di GCC.
  • Prova a costruire il tuo GCJ funzionante dalle ultime cose senza seguire uno script di build distro. Ti sfido.
8
Zan Lynx

Diverse ragioni:

  • Scrittori di compilatori "mangia il loro cibo per cani".
  • I compilatori si basano su principi ben compresi di CS.
  • I compilatori sono costruiti in modo molto specifica chiara.
  • I compilatori ottengono testato.
  • I compilatori sono non sempre molto affidabili.
6
Kramii

Di solito sono molto bravi a -O0. In effetti, se sospettiamo un bug del compilatore, confrontiamo -O0 rispetto a qualsiasi livello che stiamo cercando di usare. Livelli di ottimizzazione più elevati comportano un rischio maggiore. Alcuni lo sono persino deliberatamente, ed etichettati come tali nella documentazione. Ne ho incontrati molti (almeno cento durante il mio tempo), ma recentemente stanno diventando molto più rari. Tuttavia, alla ricerca di buoni numeri di marchio (o altri parametri di riferimento importanti per il marketing), la tentazione di superare i limiti è grande. Qualche anno fa abbiamo avuto problemi in cui un fornitore (per non nominare) ha deciso di rendere predefinita la violazione della parentesi, piuttosto che qualche opzione di compilazione chiaramente etichettata.

Può essere difficile diagnosticare un errore del compilatore rispetto a dire un riferimento di memoria vagante, una ricompilazione con diverse opzioni può semplicemente confondere il posizionamento relativo degli oggetti dati nella memoria, quindi non sai se si tratta di Heisenbug del codice sorgente o di un buggy compilatore. Inoltre, molte ottimizzazioni apportano modifiche legittime all'ordine delle operazioni o anche semplificazioni algebriche della tua algebra, e queste avranno proprietà diverse rispetto all'arrotondamento in virgola mobile e under/overflow. È difficile districare questi effetti dai bug REALI. L'elaborazione in virgola mobile hard core è difficile per questo motivo, poiché i bug e la sensibilità numerica spesso non sono facili da districare.

5
Omega Centauri

I bug del compilatore non sono poi così rari. Il caso più comune è che un compilatore riporti un errore sul codice che dovrebbe essere accettato o che un compilatore accetti il ​​codice che avrebbe dovuto essere rifiutato.

5
kevin cline

Hai mai riscontrato un bug nel compilatore stesso? Cos'è stato e come hai capito che il problema era nel compilatore stesso?

Yup!

I due più memorabili sono stati i primi due che abbia mai incontrato. Erano entrambi nel compilatore Lightspeed C per Mac 680x0 tra il 1985 e il 2007.

Il primo è stato in cui, in alcune circostanze, l'operatore di postincremento intero non ha fatto nulla - in altre parole, in un particolare pezzo di codice, "i ++" semplicemente non ha fatto nulla per "i". Mi stavo strappando i capelli finché non ho visto uno smontaggio. Quindi ho fatto l'incremento in un modo diverso e ho inviato una segnalazione di bug.

Il secondo era un po 'più complicato ed era davvero una "caratteristica" poco considerata che andava male. I primi Mac avevano un sistema complicato per eseguire operazioni su disco di basso livello. Per qualche motivo che non ho mai capito - probabilmente a che fare con la creazione di file eseguibili più piccoli - piuttosto che il compilatore che stava semplicemente generando le istruzioni di funzionamento del disco sul posto nel codice oggetto, il compilatore Lightspeed chiamava una funzione interna, che in fase di esecuzione generava l'operazione del disco istruzioni in pila e saltò lì.

Funzionava perfettamente su 68000 CPU, ma quando si eseguiva lo stesso codice su una CPU 68020, spesso faceva cose strane. Si è scoperto che una nuova funzionalità del 68020 era una cache di istruzioni a 256 byte per l'istruzione primitiva. In questi primi tempi con cache della CPU, non aveva la nozione che la cache fosse "sporca" e necessitava di essere ricaricata; Immagino che i progettisti di CPU di Motorola non abbiano pensato al codice di auto-modifica. Quindi, se hai eseguito due operazioni su disco abbastanza vicine tra loro nella sequenza di esecuzione e il runtime di Lightspeed ha creato le istruzioni effettive nella stessa posizione nello stack, la CPU penserebbe erroneamente di avere un hit nella cache delle istruzioni ed eseguire la prima operazione su disco due volte.

Ancora una volta, capire che ci voleva un po 'di tempo per scavare con un disassemblatore e un sacco di passaggi singoli in un debugger di basso livello. La mia soluzione era quella di anteporre a ogni operazione del disco una chiamata a una funzione che eseguiva 256 istruzioni "NOP", che inondavano (e quindi svuotavano) la cache delle istruzioni.

Da allora, nel corso dei 25 anni, ho visto sempre meno bug nel compilatore. Penso che ci siano un paio di ragioni per questo:

  • Esiste una serie sempre crescente di test di convalida per i compilatori.
  • I compilatori moderni sono in genere divisi in due o più parti, una delle quali genera codice indipendente dalla piattaforma (ad esempio LLVM che mira a ciò che potresti considerare una CPU immaginaria) e un'altra che lo traduce in istruzioni per l'hardware di destinazione reale. Nei compilatori multipiattaforma, la prima parte viene utilizzata ovunque, quindi ottiene tonnellate di test nel mondo reale.
4
Bob Murphy

Trovato un evidente errore in Turbo Pascal 5,5 anni fa. Errore presente nella versione precedente (5.0) o successiva (6.0) del compilatore. E uno che avrebbe dovuto essere facile da testare, in quanto non era affatto una maiuscola (solo una chiamata che non è quella comunemente usata).

In generale, certamente i costruttori di compilatori commerciali (piuttosto che i progetti di hobby) avranno in atto procedure di controllo qualità e test molto estese. Sanno che i loro compilatori sono i loro progetti di punta e che i difetti avranno un aspetto molto brutto su di loro, peggio di quanto sembrerebbero su altre società che realizzano la maggior parte degli altri prodotti. Gli sviluppatori di software sono un gruppo che non perdona, i nostri fornitori di strumenti ci deludono, è probabile che cerchiamo alternative piuttosto che aspettare una correzione dal fornitore, e molto probabilmente lo comunicheremo ai nostri colleghi che potrebbero seguire il nostro esempio. In molti altri settori non è così, quindi la potenziale perdita per un produttore di compilatori a causa di un grave bug è di gran lunga superiore a quella di un produttore di software di editing video.

4
jwenting

Sì, ho riscontrato un bug nel compilatore ASP.NET proprio ieri:

Quando si utilizzano modelli fortemente tipizzati nelle viste, esiste un limite al numero di parametri che i modelli possono contenere. Ovviamente non possono essere necessari più di 4 parametri di template, quindi entrambi gli esempi seguenti rendono troppo complicato il compilatore:

ViewUserControl<System.Tuple<type1, type2, type3, type4, type5>>

Non verrà compilato così com'è ma lo sarà se type5 è rimosso.

ViewUserControl<System.Tuple<MyModel, System.Func<type1, type2, type3, type4>>>

Compilerebbe se type4 è rimosso.

Nota che System.Tuple ha molti sovraccarichi e può richiedere fino a 16 parametri (è pazzesco lo so).

3
user8685

Si verificano errori del compilatore, ma si tende a trovarli in angoli strani ...

C'è stato uno strano bug nel compilatore VAX VMS C della Digital Equipment Corporation negli anni '90

(Indossavo una cipolla sulla cintura, come era la moda all'epoca)

Un punto e virgola estraneo che precede un ciclo for verrà compilato come corpo del ciclo for.

f(){...}
;
g(){...}

void test(){
  int i;
  for ( i=0; i < 10; i++){
     puts("hello");
  }
}

Sul compilatore in questione, il ciclo viene eseguito solo una volta.

vede

f(){...}
g(){...}

void test(){
  int i;
  for ( i=0; i < 10; i++) ;  /* empty statement for fun */

  {
     puts("hello");
  }
}

Mi è costato molto tempo.

La versione precedente del compilatore PIC C che noi (eravamo soliti) infliggere agli studenti di esperienza lavorativa non era in grado di generare codice che utilizzava correttamente l'interruzione ad alta priorità. Hai dovuto aspettare 2-3 anni e aggiornare.

Il compilatore MSVC 6 aveva un bug elegante nel linker, si sarebbe verificato un errore di segmentazione e sarebbe morto di tanto in tanto senza motivo. Una build pulita generalmente l'ha risolto (ma sospiro non sempre).

3
Tim Williscroft

Quando il comportamento del tuo software è diverso quando compilato con -O0 e -O2, hai trovato un bug del compilatore.

Quando il comportamento del tuo software è diverso da quello che ti aspetti, è probabile che il bug sia nel tuo codice.

2
mouviciel

In alcuni domini, come il software avionico, ci sono requisiti di certificazione estremamente elevati, sul codice e sull'hardware, nonché sul compilatore. A proposito di quest'ultima parte, esiste un progetto che mira a creare un compilatore C formalmente verificato, chiamato Compcert . In teoria, questo tipo di compilatore è affidabile come viene.

2
Axel

Ho visto diversi bug del compilatore, ne ho riportati alcuni me stesso (in particolare, in F #).

Detto questo, penso che i bug del compilatore siano rari perché le persone che scrivono compilatori sono generalmente molto a proprio agio con i concetti rigorosi dell'informatica che li rendono davvero consapevoli delle implicazioni matematiche del codice.

Molti di loro presumibilmente hanno molta familiarità con cose come il calcolo lambda, la verifica formale, la semantica denotazionale ecc. - cose che un programmatore medio come me riesce a malapena a comprendere.

Inoltre, di solito esiste una mappatura abbastanza semplice dall'input all'output nei compilatori, quindi il debug di un linguaggio di programmazione è probabilmente molto più semplice del debug, diciamo, di un motore di blog.

2
Rei Miyasaka

Ho trovato un bug nel compilatore C # non molto tempo fa, puoi vedere come Eric Lippert (che è nel team di progettazione C #) ha capito quale fosse il bug qui .

Oltre alle risposte già fornite, vorrei aggiungere alcune altre cose. I progettisti di compilatori sono spesso programmatori estremamente bravi. I compilatori sono molto importanti: la maggior parte della programmazione viene eseguita utilizzando i compilatori, quindi è indispensabile che il compilatore sia di alta qualità. È quindi nel migliore interesse delle aziende che producono compilatori metterci le persone migliori (o almeno, molto brave: ai migliori potrebbe non piacere la progettazione di compilatori). Microsoft vorrebbe che i compilatori C e C++ funzionassero correttamente, altrimenti il ​​resto dell'azienda non può svolgere il proprio lavoro.

Inoltre, se stai costruendo un compilatore davvero complesso, non puoi semplicemente hackerarlo insieme. La logica alla base dei compilatori è sia altamente complessa che facile da formalizzare. Pertanto, questi programmi saranno spesso realizzati in modo molto "solido" e generico, il che tende a generare meno bug.

2
Alex ten Brink