In questa lezione testeremo il nostro ambiente di sviluppo con il primo programma di esempio, il classico HelloWorld.
Il listato che esaminiamo è un po' diverso dai soliti proposti come primo esempio di codice in linguaggio C++. Proponiamo una versione leggermente più articolata allo scopo di mettere in evidenza, fin da subito, una delle caratteristiche principali del linguaggio, che è anche il principio fondamentale che ne ha ispirato la definizione: la programmazione orientata agli oggetti (o OOP).
Analizzeremo prima le macro sezioni che compongono il programma e poi cercheremo di scendere al dettaglio delle singole istruzioni. Manterremo tuttavia un livello di approfondimento non troppo specifico, lasciando alle lezioni successive una trattazione più completa.
/*
Il primo programma di esempio:
HelloWorld!
*/
#include <iostream>
// Definizione della classe Greater
class Greater
{
public:
void sayHello()
{
std::cout << "Hello World!" << std::endl;
}
};
// Funzione main: il punto di inizio per l'esecuzione del programma
int main()
{
Greater g;
g.sayHello();
return 0;
}
}
Le prime righe contengono un commento con una descrizione del contenuto del listato. L'uso di /*
e */
, rispettivamente all'inizio ed alla fine del commento, ci consentono di suddividere il testo in più righe. Molto spesso, le prime righe di commento sono dedicate anche ad altre informazioni utili, quali autore, data di rilascio, e soprattutto il tipo di licenza che vincola l'utilizzo del codice.
Di seguito troviamo, preceduta dal simbolo #
, una direttiva al preprocessore. Per "direttiva al preprocessore" si intende un tipo di istruzione che non sarà eseguita nel nostro programma, ma durante la fase preliminare del processo di trasformazione del codice sorgente in eseguibile. In questo caso stiamo informando il compilatore del fatto che il nostro programma farà uso di funzionalità incluse in una libreria standard C++.
Nel caso specifico ci serve includere nel nostro sorgente l'header file iostream. Vedremo meglio nel seguito l'effetto di tale operazione.
Il blocco di codice successivo, preceduto da un breve commento su singola linea marcato da //
, è invece costituito dalla definizione della classe Greater
, in cui definiamo il metodo sayHello()
per stampare a schermo il nostro messaggio di saluto. È nella definizione di questo metodo che facciamo uso di alcuni oggetti e operatori definiti nell'header <iostream>
, che implementano alcune funzionalità di input/output (I/O) di base.
Al fine di rendere il metodo utilizzabile in un contesto differente da quello della definizione della classe, esso è incluso nella sezione pubblica, marcata dalla parola chiave public
, seguita da :
.
Infine l'ultimo blocco del listato è una parte fondamentale di ogni programma C++: la funzione main().
La funzione main()
è una funzione speciale che costituisce il punto di inizio dell'esecuzione del programma. Indifferentemente da come sia strutturato il nostro codice, su uno o più listati, e dalla sua complessità, la funzione main()
viene invocata per prima ed il momento in cui essa ritorna concide con la fine del programma stesso.
In questo caso particolare, per la sua definizione occorrono poche istruzioni: l'istanziazione di un oggetto di tipo Greater e l'invocazione del suo metodo sayHello()
. Con l'ultima istruzione, la funzione main()
restituisce il valore 0
, che solitamente si associa ad una terminazione normale del programma, senza errori da notificare al contesto del chiamante.
In questo esempio di codice così piccolo abbiamo cercato di condensare le componenti principali del linguaggio, necessarie per la costruzione di programmi arbitrariamente complessi: commenti, direttive al preprocessore, classi, la funzione main()
eccetera, allo scopo di offrire un primo assaggio della programmazione in C++.
Avremmo potuto ottenere lo stesso risultato con molte righe di codice in meno, e senza tirare in ballo la programmazione a oggetti, ma non è con questo spirito che è nato il C++. Ribadiamo ancora una volta che non è una variante del C o un linguaggio di programmazione a oggetti di più basso livello rispetto a Java o C#, ma un linguaggio a parte, che richiede una certa dedizione per l'applicazione efficace dei suoi costrutti.
Nel caso di un'applicazione non banale, ad esempio, la classe Greater
si presta ad essere espansa in modo tale da poter gestire la corretta profilazione degli utenti all'avvio dell'applicazione, o mostrare una schermata iniziale con informazioni utili riguardo la versione, la licenza, la disponibilità di aggiornamenti mentre il processo principale completa la fase di caricamento.
La chiave di lettura è: usiamo sempre e ad ogni livello i costrutti e i design pattern più potenti che il linguaggio che usiamo ci mette a disposizione, perchè questa pratica è alla base dell'espandibilità e della longevità delle nostre applicazioni.
Il processo di compilazione
Useremo il listato precedente anche per descrivere brevemente il processo di compilazione, illustrato schematicamente nella figura successiva.
Il processo si articola in quattro stadi principali, ognuno dei quali produce un risultato intermedio che possiamo visualizzare usando g++.
Esecuzione delle direttive al preprocessore
La fase preliminare è quella in cui vengono inclusi nei sorgenti i file header, vengono espanse tutte le macro e vengono specializzati i template (tutti concetti che affronteremo più avanti nella guida). Con il comando seguente generiamo il file main.ii, che contiene il file sorgente modificato:
g++ -E main.cpp > main.ii
Sebbene la lettura sia ardua, può essere istruttivo osservare quanto codice viene aggiunto al nostro in base alle librerie e ai costrutti che usiamo.
Produzione del codice assembly
La compilazione è la fase più delicata e complessa dell'intero processo. A partire dai sorgenti modificati dal preprocessore, essa genera dei file in codice assembly, un linguaggio di programmazione di basso livello, molto più vicino al linguaggio macchina finale di cui sarà composto il nostro eseguibile.
Il risultato del comando seguente è il file main.s, in linguaggio assembly, che possiamo ispezionare da un comune editor di testo.
g++ -S main.ii
Produzione del codice oggetto
Il codice tradotto in linguaggio assembly è adesso pronto per essere ulteriormente tradotto in linguaggio macchina da un progamma noto come assembler. I file così ottenuti sono file binari il cui contenuto è detto "codice oggetto". Con il comando seguente possiamo generare il file main.o, contenente il codice oggetto:
g++ -c -o main.o main.s
Anche nel caso di un unico file oggetto, però esistono ancora delle differenze tra codice oggetto e file eseguibile. A questo stadio, per esempio, manca ancora il codice per il caricamento dell'eseguibile da parte del sistema operativo, o da un loader nel caso di micro-controllori.
Linking
L'ultima fase è quella del linking, cioè la composizione di tutti i file oggetto in un unico file eseguibile o in una libreria condivisa. Il comando seguente, che abbiamo visto in precedenza, trasforma infine i codici oggetto nell'eseguibile di nome HelloWorld:
g++ -o HelloWorld main.o
Anche in seguito alla generazione del codice oggetto, è tuttavia possibile che il processo di linking fallisca, a causa della mancata definizione o esportazione di alcuni simboli, o quando facciamo riferimento nel nostro codice a librerie che non sono installate nel nostro sistema.
Tuttavia, il compilatore fornisce solitamente informazioni dettagliate che ci indirizzano alle cause del problema, siano esse errori sintattici o di linking, e pertanto occorre prestare attenzione agli eventuali messaggi prodotti dal processo di compilazione.