La differenza tra un compilatore e un interprete
Il Dicembre 31, 2021 da adminSecondo le loro definizioni, la differenza tra un compilatore e un interprete sembra abbastanza chiara:
- l’interprete è un programma che esegue direttamente le istruzioni scritte in un linguaggio di programmazione
- il compilatore è un programma che trasforma il codice sorgente in un linguaggio di basso livello
Se si scava più a fondo, però, si trova una certa confusione tra i due.
In effetti un interprete potrebbe tradurre il linguaggio sorgente in una forma intermedia, per accelerare l’esecuzione. Questo è ciò che di solito accade con un linguaggio che si basa su una macchina virtuale. Questo porta naturalmente ad alcune domande:
Tutti i linguaggi che usano una macchina virtuale sono interpretati?
Sono tutti effettivamente compilati?
Si potrebbe dire entrambe le cose: un linguaggio viene prima compilato in una forma/linguaggio intermedio e poi questa cosa intermedia viene interpretata a tempo di esecuzione. Il che porta anche a un’altra questione, un compilatore e un interprete non dovrebbero essere pensati come un unico programma, ma più come un gruppo di programmi, un sistema. Quello che voi, come utente, pensate come un compilatore può in realtà includere più di un programma. Per esempio, può includere un linker: un programma che combina diversi file oggetto in un unico file, in modo che possa essere usato più facilmente. Qualcosa di simile si potrebbe dire di un interprete.
Puoi dirmi tutto sui compilatori &interpreti?
Quindi, quali sono tutti i pezzi che compongono un compilatore o un interprete? Si può cercare una risposta precisa e tecnica a queste domande nel mondo accademico. Oppure si possono trovare discussioni su questi argomenti su StackOverflow.
Quello che conta davvero per noi sviluppatori, o anche per noi creatori di un linguaggio, è quali sono le differenze nel lavorare con loro. Entrambi hanno vantaggi e svantaggi, e infatti alcuni linguaggi possono avere sia un interprete che un compilatore, o più di uno. Questo è ciò che vedremo.
Il punto principale rimane: un interprete esegue il codice ora, un compilatore prepara il codice sorgente per un’esecuzione che viene dopo. Tutte le differenze pratiche discendono da questi diversi obiettivi.
Come si distribuisce un programma
In termini pratici una differenza importante è che un compilatore genera un programma autonomo, mentre un programma interpretato ha sempre bisogno dell’interprete per funzionare.
Una volta che si ha un programma compilato lo si può eseguire senza bisogno di installare altro. Questo semplifica la distribuzione. D’altra parte l’eseguibile lavora su una piattaforma specifica: diversi sistemi operativi e diversi processori hanno bisogno di diverse versioni compilate. Per esempio, un programma C++ compilato potrebbe funzionare su un computer con un processore x86, ma non su uno con un chip ARM. Oppure potrebbe funzionare su un sistema Linux, ma non su uno Windows.
Se si interpreta un programma si può distribuire la stessa copia agli utenti su piattaforme diverse. Tuttavia avranno bisogno di un interprete che funzioni sulla loro specifica piattaforma. Potreste distribuire il codice sorgente originale o una forma intermedia. Un modo intuitivo di vedere un interprete è questo: è come la funzione eval
in JavaScript. Funziona ovunque funzioni JavaScript, ma ha bisogno di un interprete JavaScript per quella piattaforma per funzionare.
Supporto multipiattaforma
Questa è una differenza tecnica che porta a importanti conseguenze reali: è più facile fare programmi multipiattaforma con un linguaggio di programmazione interpretato.
Questo perché, per la maggior parte, si sta solo creando un programma per la piattaforma interprete. Sarà l’interprete stesso a tradurlo nella forma appropriata per la piattaforma reale (ad esempio, Windows/Linux e x86/ARM). Naturalmente, ci sono ancora alcune differenze in ogni piattaforma di cui dovete essere consapevoli. Un esempio comune è il carattere separatore di directory.
Quando compilate un programma, invece, dovete occuparvi voi stessi di tutte le piccole differenze tra ogni piattaforma. Questo accade in parte perché i linguaggi compilati tendono ad essere linguaggi di basso (er) livello, come il C++, quindi ti danno un accesso più basso al sistema e quindi più responsabilità. Ma un’altra ragione è che tutte le librerie che state usando hanno bisogno di supportare diverse piattaforme. Quindi, se loro non supportano Windows, voi non potete supportare Windows.
La velocità ha più facce
Ancora una volta, nel caso della velocità, abbiamo una sorta di paradosso: un compilatore è sia più veloce che più lento di un interprete. Molte persone sanno che un programma compilato è molto più veloce di uno interpretato, ma questo non è il quadro completo. Un programma compilato è più veloce da eseguire di un programma interpretato, ma ci vuole più tempo per compilare ed eseguire un programma che per interpretarlo e basta.
Un compilatore produce effettivamente programmi più veloci. Ciò accade fondamentalmente perché deve analizzare ogni dichiarazione solo una volta, mentre un interprete deve analizzarla ogni volta. Inoltre, un compilatore può ottimizzare il codice eseguibile che produce. Questo sia perché sa esattamente dove verrà eseguito, sia perché richiede tempo per ottimizzare il codice. Tempo che renderebbe l’interpretazione troppo lenta.
Runtime Speed Versus Development Speed
Si potrebbe pensare che questo sia un pignolo: se si compila un programma, questo viene eseguito più velocemente, il tempo che ci vuole per compilare non ha importanza. Questa è di solito l’opinione della vecchia scuola. E senza dubbio il programma risultante viene eseguito più volte di quanto viene compilato. Quindi a chi importa se lo sviluppo richiede più tempo? Beh, di sicuro ci vuole un atteggiamento “porta il dolore” allo sviluppo che è in qualche modo ammirevole. Ma cosa succede se i guadagni in termini di tempo di esecuzione non sono rilevanti, mentre le perdite di produttività dello sviluppo sono significative?
Una cosa è se si crea un sistema operativo e un’altra se si fa un’app per selfie. Persino i vostri utenti potrebbero preferire una perdita nemmeno percettibile nella velocità di esecuzione in cambio di funzioni più veloci. Non c’è una risposta univoca: in alcuni contesti la produttività conta più della velocità, in altri è vero il contrario.
I misteri del debugging
C’è un altro aspetto particolare che finisce per essere più incerto di quello che si direbbe: il debugging. Sulla carta il debugging è più facile quando si usa un interprete che quando si usa un compilatore. Questo è vero per diverse ragioni:
- con l’interprete c’è una sola versione dell’eseguibile; non c’è bisogno di una versione di debug per lo sviluppo e una di rilascio per l’utente finale
- ci sono meno bug specifici della piattaforma usando un interprete
- perché l’interprete trasforma il codice al volo, le informazioni dal codice sorgente sono ancora disponibili
- siccome l’interprete esegue un’istruzione alla volta, è più facile trovare un errore
La differenza che gli strumenti di sviluppo fanno
Mentre tutto questo è vero in pratica, questo potrebbe essere meno rilevante di quanto sembra. Infatti se pensate alla vostra esperienza, probabilmente troverete che il debugging di JavaScript è più difficile del debugging di C++. Perché è così? In parte è il design dei linguaggi stessi. JavaScript usa la tipizzazione dinamica, mentre C++ usa la tipizzazione statica. Quest’ultimo rende più facile catturare gli errori in anticipo. Ma alla fine si riduce agli strumenti di sviluppo. Compilare C++ a mano è difficile, così la maggior parte delle persone usa gli IDE per svilupparci. D’altra parte, si possono facilmente usare editor di testo e strumenti a linea di comando per sviluppare in JavaScript.
Questo significa che, in termini pratici, se si sviluppa con C++, si può anche fare il debug di C++. Invece si può sviluppare con JavaScript senza sapere come fare il debug corretto in JavaScript.
Detto questo, se li mettiamo nello stesso contesto, ognuno con un grande IDE e strumenti di supporto, la situazione torna alla normalità. Infatti molti ambienti interpretati sono usati da persone che vogliono imparare ad usare un nuovo linguaggio. È più facile testare, e trovare ciò che è giusto o sbagliato, guardando ciò che accade riga per riga e in tempo reale.
Sommario
Abbiamo visto le principali differenze che contano tra un compilatore e un interprete. Ancora più importante, abbiamo visto che le conseguenze delle diverse filosofie sono più importanti di quelle tecniche. In breve, ci sono culture che accompagnano certe scelte tecniche che finiscono per essere rilevanti da sole. Se volete velocità e facilità di sviluppo, sceglierete tutte le tecnologie per la velocità e non solo una. E i tuoi utenti ti seguiranno.
Questo è un aspetto cruciale su cui riflettere, specialmente se vuoi creare il tuo linguaggio di programmazione. Rasmus Lerdorf ha creato PHP per essere facile da usare. E in effetti era incredibilmente più facile da usare rispetto alle alternative, almeno al momento della sua creazione. Ma è iniziato più come una libreria che come un linguaggio. E mentre è migliorato molto, soffre ancora dei suoi inizi. Si può ancora creare del buon codice PHP, ma lo fanno meno utenti del solito. Perché se hai solo bisogno di qualcosa che funzioni, sicurezza, manutenzione, ecc, tutto il resto viene dopo.
Se vuoi sapere come puoi praticamente costruire un interprete o un compilatore per il tuo linguaggio, potresti voler dare un’occhiata alle risorse per creare un linguaggio di programmazione. Se volete imparare questo, e tutto ciò di cui avete bisogno per creare il vostro linguaggio, dovete solo scegliere un grande libro, amato da bambini e adulti, su come creare linguaggi pragmatici e leggeri.
5 cose da fare bene quando si costruisce un linguaggio
Ricevi la lista di controllo via email e ricevi altri consigli su come costruire linguaggi
Lascia un commento