I programmi C: un primo approccio 

E' giunto il momento di cominciare ad addentrarsi nei segreti del C. All'approccio tecnico seguito dalla maggior parte dei manuali sull'argomento, è sembrata preferibile un'esposizione la più discorsiva possibile (ma non per questo, almeno nelle intenzioni, approssimativa). Le regole sintattiche saranno presentate sulla base di esempi, semplificati ma comunque realistici. Dedichiamoci dunque al nostro primo programma in C, un ipotetico CIAO.C

#include <stdio.h>

void main(void);

void main(void)
{
    printf("Ciao Ciao!\n");
}

Il programma non è un gran che, ma dalle sue poche righe possiamo già ricavare un certo numero di informazioni utili. In C, ogni riga contenente istruzioni o chiamate a funzioni o definizioni di dati si chiude con un punto e virgola, e costituisce una riga logica. Il punto e virgola segnala al compilatore il termine della riga logica, in quanto essa può essere suddivisa in più righe fisiche semplicemente andando a capo. Nello scrivere un programma C si ha molta libertà nel gestire l'estetica del sorgente: il programma appena visto avrebbe potuto essere scritto così: 

#include <stdio.h>
void main(void); void main(void) {printf("Ciao Ciao!\n");}

oppure, ad esempio: 

#include <stdio.h>
void main
(void); void 
main(void) {printf("Ciao Ciao!\n");}

Solo la prima riga deve rimanere  isolata[1]; per il resto il compilatore non troverebbe nulla da ridire, ma forse i nostri poveri occhi sì... Gli "a capo", gli indent (rientri a sinistra), le righe vuote, sono semplicemente stratagemmi tipografici ideati per rendere più leggibile il sorgente, e per dare qualche indicazione visiva sulla struttura logica del programma. Un po' di chiarezza è indispensabile; d'altra parte si tratta ormai di vere e proprie convenzioni, seguite dalla grande maggioranza dei programmatori C, addirittura codificate in testi ad esse dedicati. Tuttavia, lo ripetiamo, esse non fanno parte dei vincoli sintattici del linguaggio. Attenzione: il C è un linguaggio case­sensitive, il cui compilatore, cioè, distingue le maiuscole dalle minuscole (a differenza, ad esempio, del Basic). E' vero, dunque, che 

    printf("Ciao Ciao!\n");

potrebbe essere scritta 

    printf(
    "Ciao Ciao!\n"
    );

ma non si potrebbe scrivere PRINTF o Printf: non sarebbe la stessa cosa... Il risultato sarebbe una segnalazione di errore da parte del linker, che non riuscirebbe a trovare la funzione nella libreria. La prima riga del programma è una direttiva al preprocessore (#include): questo inserisce tutto il testo del file STDIO.H nel nostro sorgente, a partire dalla riga in cui si trova la direttiva stessa. A cosa serve? Nel file STDIO.H ci sono altre direttive al preprocessore e definizioni che servono al compilatore per tradurre correttamente il programma. In particolare, in STDIO.H è descritto (vedremo come più avanti) il modo in cui la funzione printf() si interfaccia al programma che la utilizza. Ogni compilatore C è accompagnato da un certo numero di file .H, detti include file o header file, il cui contenuto è necessario per un corretto utilizzo delle funzioni di libreria (anche le librerie sono fornite col compilatore). Il nome dell'include file è, in questo caso, racchiuso tra parentesi angolari ("<" 5e ">"): ciò significa che il preprocessore deve ricercarlo solo nelle directory specificate nella configurazione del compilatore. Se il nome fosse racchiuso tra virgolette (ad esempio: "MYSTDIO.H"), il preprocessore lo cercherebbe prima nella directory corrente, e poi in quelle indicate nella configurazione. Da non dimenticare: le direttive al preprocessore non sono mai chiuse dal punto e virgola. La riga 

void main(void);

descrive le regole di interfaccia della funzione main(). Si noti che al termine della riga c'è il punto e virgola. La riga che segue è apparentemente identica: 

void main(void)

ma in essa il punto e virgola non compare. La differenza è notevole, infatti l'assenza del ";" ci segnala che questa riga è l'inizio della definizione della funzione main(), cioè della parte di programma che costituisce, a tutti gli effetti, la funzione main() stessa. Che cosa sia una funzione, e come lavori, sarà oggetto di accaniti sforzi mentali a suo tempo. 

Per adesso ci limitiamo ad osservare che dopo la riga che ne descrive l'interfaccia c'è una parentesi graffa aperta: questa segna l'inizio del codice eseguibile della funzione, che si chiude (vedere l'ultima riga del listato) con una graffa chiusa, non seguita da alcun punto e virgola. Tutto quello che sta tra le due graffe è il corpo della funzione (function body) e definisce le azioni svolte dalla funzione stessa: può comporsi di istruzioni, di chiamate a funzione, di definizioni di variabili... 

In pratica ogni funzione può essere in qualche modo paragonata ad un programma a se stante. La main() è una funzione molto particolare: tutti i programmi C devono contenere una ed una sola funzione main() e l'esecuzione del programma inizia dalla prima riga di questa. 

Quante istruzioni C sono utilizzate da CIAO.C? La risposta è... nemmeno una! La #include, abbiamo detto, è una direttiva al preprocessore, e come tale viene da questo elaborata ed eliminata dal testo sorgente (infatti viene sostituita con il contenuto del file .H) passato in input al compilatore. La descrizione dell'interfaccia, detta anche prototipo, di main(), informa il compilatore che da qualche parte, nel programma, c'è una funzione main() che si comporta in un certo modo: dunque non è un'istruzione. 

La definizione di main(), a sua volta, in quanto tale non è un'istruzione; semmai ne può contenere. Ma l'unica riga contenuta nella definizione di main() è la chiamata alla funzione printf(), la quale, essendo una funzione, non è un'istruzione (ovvio, no?). In C, un nome seguito da parentesi tonde aperta e chiusa (eventualmente racchiudenti qualcosa) è una chiamata a funzione. In particolare, printf() è esterna al compilatore, ma fa parte di un gruppo di funzioni inserite nella libreria che accompagnano praticamente tutte le implementazioni esistenti di compilatori C: per questo essa è considerata una funzione standard del C. La funzione printf()  scrive a video[2] la sequenza di caratteri, racchiusa tra virgolette, specificata tra le parentesi tonde. Una sequenza di caratteri tra virgolette è una stringa. Quella dell'esempio si chiude con i caratteri '\' e 'n', che in coppia hanno, nel linguaggio C, un significato particolare: "vai a capo". In pratica tutte le operazioni di interazione tra i programmi e lo hardware, il  firmware[3] ed il sistema operativo sono delegate a funzioni (aventi interfaccia più o meno standardizzata) esterne al compilatore, il quale non deve dunque implementare particolari capacità di generazione di codice di I/O, peculiari per il sistema al quale è destinato. 

Abbiamo scritto un programma C senza utilizzare quasi nulla del C. Stiamo lavorando in ambiente DOS? Bene, è sufficiente compilarlo per ottenere l'eseguibile. Vogliamo utilizzarlo su una macchina Unix? Non dobbiamo fare altro che trasportare il sorgente su quella macchina e compilarlo nuovamente su di essa... 


OK, andiamo avanti a leggere il libro... 

Non ci ho capito niente! Ricominciamo...