Last Updated:

Come funzionano i programmi in C

Dario Fadda C/C++

Linguaggi interpretati e compilati

Si è detto che il linguaggio di programmazione consente di esprimere gli algoritmi in modo "umano", incomprensibile alla macchina, la quale è in grado di eseguire esclusivamente istruzioni codificate in modo binario, cioè con una sequenza di 1 e 0 (che rappresentano, a loro volta, la presenza o l'assenza di una tensione elettrica).

È perciò indispensabile che il sorgente del programma (cioè il file contenente il testo scritto dal programmatore in un dato linguaggio di programmazione) venga elaborato e trasformato in una sequenza di codici binari significativi per l'elaboratore.

Gli strumenti generalmente utilizzati allo scopo rientrano in due categorie: interpreti e compilatori.

L'Interprete

L'interprete è un programma in grado di leggere un sorgente in un certo linguaggio e, istruzione per istruzione, verificarne la sintassi, effettuarne la traduzione in linguaggio macchina e far eseguire al microprocessore della macchina il codice binario generato. La logica con cui l'interprete lavora è proprio quella di un... interprete: se la medesima istruzione viene eseguita più volte (ad esempio perché si trova all'interno di un ciclo), ad ogni iterazione ne viene verificata la correttezza sintattica, ne è effettuata la traduzione, e così via. L'esecuzione del programma può essere interrotta in qualunque momento ed è possibile modificarne una parte, per poi riprendere l'esecuzione dal punto di interruzione. L'interprete è inoltre in grado di interrompere spontaneamente l'esecuzione quando rilevi un errore di sintassi, consentire al programmatore la correzione dell'errore e riprendere l'esecuzione dall'istruzione appena modificata.

È facile intuire che la programmazione interpretata facilita enormemente le varie fasi di sviluppo e correzione del programma; tuttavia essa presenta alcuni pesanti svantaggi: il programma "gira" lentamente (perché ogni istruzione deve essere sempre verificata e tradotta, anche più volte nella medesima sessione di lavoro, prima di essere eseguita) ed inoltre può essere eseguito solo ed esclusivamente attraverso l'interprete.

Un esempio classico di linguaggio interpretato (nonostante ve ne siano in commercio versioni compilate o miste) è il Basic.

Il Compilatore

Anche in questo caso l'obiettivo di fondo è tradurre in linguaggio macchina un sorgente scritto in un linguaggio di programmazione perché l'elaboratore sia in grado di eseguirlo; tuttavia l'approccio al problema è sostanzialmente diverso.

Il sorgente viene letto dal compilatore, che effettua il controllo sintattico sulle istruzioni e le traduce in linguaggio macchina. Il risultato della traduzione è scritto in un secondo file, detto object file. Questo non è ancora eseguibile dal microprocessore, in quanto non incorpora il codice binario delle funzioni esterne al linguaggio: è dunque necessaria una fase ulteriore di elaborazione, alla quale provvede il linker, che incorpora nell'object file gli object file contenenti le funzioni esterne, già compilate in precedenza, solitamente raccolti in "contenitori" detti librerie. Il linker produce in output un terzo file, il programma vero e proprio, direttamente eseguibile dal microprocessore con la sola intermediazione del sistema operativo.

Per eseguire il programma, dunque, non servono né compilatore o linker, né, tantomeno, il file sorgente.

I vantaggi rispetto all'interprete, in termini di velocità e semplicità di esecuzione, sono evidenti, a fronte di una maggiore complessità del ciclo di sviluppo. Infatti il compilatore, nel caso in cui rilevi errori nel sorgente, li segnala e non produce alcun object file. Il programmatore deve analizzare il sorgente, correggere gli errori e ritentare la compilazione: detta sequenza va ripetuta sino a quando, in assenza di segnalazioni d'errore da parte del compilatore, viene prodotto un object file pronto per l'operazione di linking. Anche in questa fase potranno verificarsi errori: il caso classico è quello della funzione esterna non trovata nella libreria. Anche questa volta occorre analizzare il sorgente, correggere l'errore (il nome della funzione potrebbe essere stato digitato in modo errato) e ripetere non solo il linking, ma anche la compilazione.

Solo in assenza di errori tanto nella fase di compilazione quanto in quella di linking si può ottenere un file eseguibile; in altre parole: il programma funzionante.

Il C rientra a pieno titolo nella folta schiera dei linguaggi compilati (insieme a Cobol e Fortran, per fare un paio di esempi).

Quale dei due?

Come si vede, sia il compilatore che l'interprete portano con sé vantaggi e svantaggi peculiari. Quale delle due tecniche utilizzare, allora?

Al riguardo si può osservare che la finalità di un programma non è "essere sviluppato", ma servire "bene" allo scopo per il quale viene creato; in altre parole esso deve essere semplice da utilizzare e, soprattutto, efficiente. La scelta del compilatore è quindi d'obbligo per chi intenda realizzare applicazioni commerciali o, comunque, di un certo pregio.

L'interprete si pone quale utile strumento didattico per i principianti: l'interattività nello sviluppo dei programmi facilita enormemente l'apprendimento del linguaggio.

In molti casi, comunque, la scelta è obbligata: per quel che riguarda il C, non esistono in commercio interpreti in grado di offrire un valido supporto al programmatore, al di là dell'apprendimento dei primi rudimenti del linguaggio. L'utilizzo del compilatore è imprescindibile anche per la realizzazione di programmi semplici e "senza troppe pretese"; va osservato, in ogni caso, che compilatore e linker sono strumenti con i quali è possibile raggiungere elevati livelli di efficienza e produttività anche in fase di sviluppo, dopo un breve periodo di familiarizzazione.

Dall'idea all'applicazione

Vale la pena, a questo punto, di descrivere brevemente le varie fasi attraverso le quali l'idea diventa programma eseguibile, attraverso un sorgente C.

In primo luogo occorre analizzare il problema e giungere alla definizione dell'algoritmo, scindendo il problema originale in sottoproblemi di minore complessità. Banale, si tratta dell'unico approccio valido indipendentemente dal linguaggio utilizzato...

A questo punto ci si procura un editor, cioè un programma di videoscrittura, più o meno sofisticato, in grado di salvare il testo prodotto in formato ASCII puro e si inizia a digitare il programma.

"Si inizia" perché può essere utile procedere per piccoli passi, scrivendone alcune parti, compilandole ed eseguendole per controllo... Insomma, meglio non mettere troppa carne al fuoco.

Dopo avere scritto una parte di programma di "senso compiuto", tale, cioè, da poter essere compilata e consolidata onde controllarne il funzionamento, si mette da parte l'editor e si dà il file sorgente in pasto (che di solito ha estensione .C) al compilatore.

In genere il compilatore provvede a tutte le operazioni necessarie: lancia il preprocessore per effettuare le macrosostituzioni necessarie, compila il sorgente così modificato e, se non vi sono errori, lancia il linker, producendo in output direttamente il file eseguibile.

Nel caso in cui il compilatore segnali errori, il linker non viene lanciato e non è prodotto l'object file, che in questo caso sarebbe inutilizzabile. Occorre ricaricare il sorgente nell'editor ed effettuare le correzioni necessarie, tenendo presente che a volte i compilatori vengono fuorviati da errori particolari, che danno origine a molte altre segnalazioni in cascata. E' dunque meglio cominciare a coreggerli a partire dal primo segnalato; è possibile che molti altri scompaiano "da sé". A questo punto può essere nuovamente lanciato il compilatore.

Attenzione, però: il compilatore può segnalare due tipi di errori: gli error ed i warning. La presenza anche di un solo error in compilazione impedisce sempre l'invocazione del linker: si tratta per lo più di problemi nella sintassi o nella gestione dei tipi di dato per i quali è necessario l'intervento del programmatore. I warning, al contrario, non arrestano il processo e viene pertanto prodotto comunque un file eseguibile. Essi riguardano situazioni di ambiguità che il compilatore può risolvere basandosi sui propri standard, ma che è opportuno segnalare al programmatore, in quanto essi potrebbero essere la manifestazione di situazioni non desiderate, quali, ad esempio, errori di logica. E' raro che l'eseguibile generato in presenza di warning funzioni correttamente, ma non impossibile: alcuni messaggi di warning possono essere tranquillamente ignorati a ragion veduta.

Se gli errori sono segnalati dal linker, è ancora probabile che si debba intervenire sul sorgente e quindi lanciare nuovamente il compilatore; in altri casi può trattarsi di problemi di configurazione del linker stesso o di una compilazione effettuata senza indicare le librerie necessarie: è sufficiente lanciare ancora il linker dopo aver eliminato la causa dell'errore.

Il file eseguibile prodotto dal linker ha, in ambiente DOS, estensione .EXE o .COM. La scelta tra i due tipi di eseguibile dipende, oltre che dalle caratteristiche intrinseche del programma, anche dalle preferenze del programmatore. Avremo occasione di tornare su tali argomenti, esaminando i modelli di memoria e della struttura degli eseguibili.

Come si vede, il tutto non è poi così complicato...

Fontelibro Tricky C