GUIDE

Alla ricerca del compilatore perduto

10 anni fa

9 minuti

Prima le basi (gli “ingredienti”), poi la compilazione (la “cottura”, in cui accennare cosa può andar storto) e per ultimo il debugging, che presuppone aver individuato una sorta di indizi sugli errori commessi (“rifare la torta, migliorandola”).

Con questa citazione di Ragnoboy (sia benedetto maledetto circondato da poppe), inizia il quarto articolo della vostra preferita, sempreverde, incommensurabile, fantasmagorica, ineccepibile, incredibile, erotica, […], machecatzovelodicoafarev2.0 rubrica Programmazione: Parliamone.
Per i fessi o gli sfigatelli che si sono persi le precedenti puntate, il consiglio è quello di andare a mangiare sapone e olio di ricino leggendo il libretto di istruzioni della Lega al contrario; fatto quello avrete la possibilità di affacciare le vostre menti nel lurido sciogliersi di informazioni dello scorso articolo che è F O N D A M E N T A L E per evitare che scriviate immondizia nei commenti.

Nelle ultime settimane abbiamo parlato di come i linguaggi di programmazione si siano evoluti nel tempo fino a formare quell’ environment che tutti i programmatori amano ed odiano allo stesso tempo. Se vi ricordate, i primi linguaggi erano semplicemente una sorta di linguaggio molto simile a quello utilizzato direttamente dai calcolatori, scritto all’interno delle macchine dalle dolci manine di goliardici e panzuti ingegneri. Esso veniva poi eseguito durante la stessa lettura in quanto decisamente similare al linguaggio binario e, quindi, immediatamente traducibile nelle istruzioni che l’hardware poteva eseguire. Poche storie: era un mondo duro e i programmatori morivano di onanismo mentale quarantadue ore su ventiquattro.

Per questo, un bel giorno, nel lontano Giurassico 1957, dopo decenni passati a cercare una soluzione per tutti quei fazzoletti sprecati in nome della scienza, il primo linguaggio di programmazione compilato venne alla luce ed il mondo cambiò. Non fate i pidocchiosi ed andate a leggervi il secondo articolo di ProPa. NAU!

Ma in cosa consiste un compilatore, vi starete chiedendo. Beh, innanzitutto, non è un affare che ammucchia delle porte seriali cercando di superare Rocco in quanto ad “altezza” raggiunta. Non è neanche un tizio che riempie le scartoffie del lavoro al posto vostro.
Forse è 42, ma manca una decina di milioni di anni per scoprirlo.
Un compilatore, in soldoni, è quel programma che traduce del codice scritto in un linguaggio ad alto livello in un qualcosa che il processore è in grado di leggere, cercando di essere il più fedele possibile. Ha il compito di rispettare la volontà del programmatore senza intervenire sul codice e per questo non è il miglior amico del debugger (prossimamente su LN). E’ uno strumento importantissimo per il programmatore e spesso la velocità di esecuzione del programma è direttamente proporzionale alla sua qualità (pensate che con l’ultimo aggiornamento di gcc, il più famoso compilatore quasi-universale open source, le prestazioni di molti software sono aumentate addirittura del 20%). Il suo scopo ultimo è quello di creare un eseguibile che la macchina è in grado di leggere appieno.

Non sono però tutte rose e fiori. Come succedeva per i linguaggi simil-macchina, i compilatori sono legati a doppio filo coi linguaggi target, quelli che le specifiche architetture del processori sono in grado di leggere. Fortunatamente, ad oggi molti linguaggi di programmazione (ma non tutti) utilizzano dei programmi che permettono una lettura cross-platform a discapito però di un’eventuale riduzione della velocità di esecuzione. Uno di questi linguaggi è per esempio il Java, che utilizza la Java Virtual Machine.

Questo fantastico strumento lavora semplicisticamente in tre fasi:

La fase di front end dove avviene il controllo del testo dal punto di vista grammaticale e sintattico. Il codice, infatti, per essere compilato, non deve avere tutti quegli errori che andrebbero a bloccare le fasi successive di compilazione. Non sono quindi ammesse variabili dichiarate malamente, oggetti lasciati a metà, punteggiatura mancante e tutto quello che non è descritto dal programma o dal linguaggio di programmazione. Diciamo che questa fase può essere considerata come il muro di fuoco composto da GrammarNazi che tutti gli autori di LegaNerd devono superare quando decidono di suicidarsi scrivere un articolo. In questa fase, se il compilatore trova errori di sorta, viene generato un file di log contenente tutti gli eventuali errori. Eventuali perché non tutto ciò che il software vi scrive è essenzialmente giusto; infatti, molto spesso, basta una sola parentesi dimenticata per creare il caos, morte e distruzione nel developer di turno [True Story – Se il vostro programma, scritto in C, supera le 5k righe, sperate di non lasciare mai una parentesi scoperta tutta sola soletta: ve ne potreste pentire amaramente]. Dicevamo, se il compilatore trova errori in questa fase, tutto si stoppa e la traduzione non va avanti.
SE invece tutto fila liscio, solitamente viene creato un file contenente una traduzione intermedia che va direttamente a finire in pasto al secondo livello del compilatore che procede quindi con la fase di middle end [NdA: Fantasia portami via].

La fase di middle end dove avviene il lavoro di ottimizzazione. Il codice viene smaltito di tutto quello che non è necessario e le ridondanze o le ripetizioni vengono eliminate. Per chi conosce come funziona una compressione di un file, diciamo che questa è la fase dove il codice viene compresso. E’ probabilmente la parte più importante di tutto il processo ed è quella che determinerà la qualità del codice tradotto e quindi la sua velocità di esecuzione. Questa fase è molto spesso una branca dell’ultima poiché non tutti i compilatori hanno predefinitamente il compito di ottimizzare il codice. Anche qua viene creato un altro codice sorgente intermedio che passa alla fase finale, quella di back end.

La fase di back end dove avviene il passaggio dal codice intermedio al linguaggio target, generalmente un linguaggio macchina o assembly. Questa fase è particolare poiché si basa sull’architettura della macchina target – il computer che dovrà eseguire il programma – e quindi cambia in base ad essa. Generalmente viene eseguita una seconda ottimizzazione e vengono decisi come il programma utilizzerà le risorse di sistema in modo più o meno intelligente. Il prodotto finale è il famoso eseguibile .exe per i portatori di handicap per chi usa windows. Da notare che, in assenza di un DEcompilatore, è impossibile risalire al codice originale di alto livello e, per tanto, è necessario uno studio di reverse engineering per poter sapere in che modo il programmatore ha ragionato.

Come potete notare, il processo di compilazione non è affatto semplice e molto spesso è anche incredibilmente lungo. Chi usa il computer ad un livello già più avanzato avrà potuto sperimentare i brividi durante le compilazioni di durata giornaliera. Vi capisco, mi dispiace molto per voi.
Nonostante comunque tutte le preoccupazioni che il compilatore si prende per assicurare che non possano crearsi bachi, shit happens: tutti i programmi avranno dei bug anche solo minimi che non sono dipendenti da un errore grammaticale o sintattico ma bensì da un errore logico (come magari dimenticarsi che l’area del quadrato non è lato alla seconda ma la somma dei lati moltiplicata per due… ehi… aspettate un momento!). Del resto, non siamo [ancora] macchine ed è perfettamente normale sbagliare.

Se avete letto gli scorsi articoli, ed a questo punto l’avete fatto, vi starete chiedendo sicuramente un’altra cosa: perché non hai ancora nominato quella brutta parola quale è l'interprete? DON’T PANIC! Ci stavo appunto arrivando.

All’interno della categoria dei compilatori, tanti anni fa, c’è stata diciamo una deviazione voluta da alcuni ingegneri – Sempre loro, maledetti! – del modo di scrivere linguaggi ad alto livello. Utilizzati per determinate applicazioni, vennero creati dei linguaggi che non avevano bisogno di un compilatore per essere trasformati in linguaggio macchina, ma che necessitavano invece di un Interprete. Questo tipo di programma, e quindi anche il linguaggio che è collegato ad esso, differisce da un compilatore poiché ha la funzione di tradurre il codice contemporaneamente alla lettura dello stesso. E’ quindi possibile avere dei vantaggi non facilmente applicabili nei linguaggi compilati come l’indipendenza dall’architettura e dalla piattaforma, la riflessione (l’auto-modifica da parte del programma al suo stesso codice sorgente), la tipizzazione dinamica e molto altro ancora. Questo tipo di programmazione è l’ideale per il web poiché consente di creare linguaggi di scripting facilmente utilizzabile all’interno dei contesti informatici.

Se però avete letto attentamente, avrete notato che non ho detto che i linguaggi compilati siano inferiori a quelli interpretati. I linguaggi interpretati, data la loro natura “live”, sono molto più lenti e non potranno mai essere utilizzati in contesti di basso livello come driver, kernel e così via.

Tuttavia, è bene dire che esistono compilatori per PHP e Python come esistono anche interpreti per C, quindi è prettamente impossibile standardizzare questo tipo di classificazione; inoltre, ci sono dei linguaggi come il Java che sono difficili da catalogare, poiché, prendendo sempre questo come esempio, esso è interpretato sulla carta ma, quando viene eseguito, diventa a tutti gli effetti linguaggio macchina tramite il suo specifico layer. Ci sono addirittura linguaggi come il Lisp che utilizzano entrambe le tecniche contemporaneamente!

Lentamente, queste differenze andranno ad assottigliarsi sempre di più grazie all’aumento delle velocità e tutto probabilmente diventerà interpretato se non direttamente nativo. Non ci resta quindi che aspettare o attivarci per rendere il più presto possibile presente questo futuro.

Link di approfondimento
-Compilatore
-Interprete
-Java Virtual Machine
-ProPa: Basi ed Acidi dei linguaggi di programmazione
-ProPa: Storia dei linguaggi di programmazione

[more]Mi dispiace di essermi fatto attendere. Purtroppo gli esami sono una brutta bestia ed un articolo del genere me lo devo preparare a modo. Come avete notato, ora molti termini stanno diventando in inglese ed io non posso farci nulla. Sappiate che se volete conoscere la programmazione, l’inglese ne è un fondamentale tanto quanto la matematica.

Prossimo articolo, per la gioia di @zed, deeeeeebugging![/more]

Reti Neurali per organizzare la serata perfetta
Reti Neurali per organizzare la serata perfetta
Uber-paradigmi di programmazione
Uber-paradigmi di programmazione
Debugging: la maledizione del programmatore
Debugging: la maledizione del programmatore
Basi ed acidi dei linguaggi di programmazione
Basi ed acidi dei linguaggi di programmazione
Storia dei Linguaggi di Programmazione
Storia dei Linguaggi di Programmazione
Linguaggi di programmazione: parliamone!
Linguaggi di programmazione: parliamone!
Linguaggi di programmazione: parliamone!
Linguaggi di programmazione: parliamone!