Direttive al Preprocessore

 


 

Cos'é il preprocessore ?

 

In C++ (come in C), prima che il compilatore inizi a lavorare, viene attivato un programma, detto preprocessore, che ricerca nel file sorgente speciali istruzioni, chiamate direttive.

Una direttiva inizia sempre con il carattere # (a colonna 1) e occupa una sola riga (non ha un terminatore, in quanto finisce alla fine della riga; riconosce però i commenti, introdotti da // o da /*, e la continuazione alla riga successiva, definita da \).

Il preprocessore crea una copia del file sorgente (da far leggere al compilatore) e, ogni volta che incontra una direttiva, la esegue sostituendola con il risultato dell'operazione. Pertanto il preprocessore, eseguendo le direttive, non produce codice binario, ma codice sorgente per il compilatore.

Ogni file sorgente, dopo la trasformazione operata dal preprocessore, prende il nome di translation unit. Ogni translation unit viene poi compilata separatamente, con la creazione del corrispondente file oggetto, in codice binario. Spetta al linker, infine, collegare tutti i files oggetto, generando un unico programma eseguibile.

Nel linguaggio esistono molte direttive (alcune delle quali dipendono dal sistema operativo). In questo corso tratteremo soltanto delle seguenti: #include , #define , #undef  e direttive condizionali.

 


 

Direttiva #include

 

Ci é già noto il significato della direttiva #include:

#include <filename>              oppure               #include "filename"

che determina l'inserimento, nel punto in cui si trova la direttiva, dell'intero contenuto del file con nome filename.

Se si usano le parentesi angolari, si intende che filename vada cercato nella directory di default del linguaggio; se invece si usano le virgolette, il file si trova nella directory del programma.

La direttiva #include viene usata quasi esclusivamente per inserire gli header-files (.h) ed é particolarmente utile quando in uno stesso programma ci sono più implementation-files che includono lo stesso header-file.

 


 

Direttiva #define di una costante

 

Quando il preprocessore incontra la seguente direttiva:

#define   identificatore   valore

dove, identificatore è un nome simbolico (che segue le regole generali di specifica di tutti gli altri identificatori) e valore é un'espressione qualsiasi, delimitata a sinistra da blanks o tabs e a destra da blanks, tabs o new-line (i blanks e tabs interni fanno parte di valore), sostituisce identificatore con valore in tutto il file (da quel punto in poi).

Es.              #define bla frase qualsiasi anche con "virgolette"

sostituisce (da quel punto in poi) in tutto il file la parola bla con la frase: frase qualsiasi anche con "virgolette" (la "stranezza" dell'esempio riportato ha lo scopo di dimostrare che la sostituzione é assolutamente fedele e cieca, qualunque sia il contenuto dell'espressione che viene sostituita all'identificatore; il compito di "segnalare gli errori" viene lasciato al compilatore!)

In generale la direttiva #define serve per assegnare un nome a una costante (che viene detta "costante predefinita").

Es.              #define ID_START 3457

da questo punto in poi, ogni volta che il programma deve usare il numero 3457, si può specificare in sua vece ID_START

Esistono principalmente due vantaggi nell'uso di #define:

 
In pratica la direttiva #define produce gli stessi risultati dello specificatore di tipo const; al posto della direttiva dell'esempio precedente si sarebbe potuto scrivere la dichiarazione:

const  int   ID_START  =  3457;

 


 

Confronto fra la direttiva #define e lo specificatore const

 

Vantaggi nell'uso di const:

Vantaggi nell'uso di #define:

 


 

Direttiva #define di una macro

 

Quando il preprocessore incontra la seguente direttiva:

#define   identificatore(argomenti)   espressione

riconosce una macro, che distingue dalla definizione di una costante per la presenza della parentesi tonda subito dopo identificatore (senza blanks in mezzo).

Una macro é molto simile a una funzione. Il suo uso é chiarito dal seguente esempio:
          #define   Max(a,b)   a > b ? a : b
tutte le volte che il preprocessore trova nel programma una chiamata della macro, per esempio Max(x,y), la espande, sostituendola con: x > y ? x : y

Come nel caso di definizione di una costante, anche per una macro la sostituzione avviene in modo assolutamente fedele: a parte i nomi degli argomenti, che sono ricopiati dalla chiamata e non dalla definizione, tutti gli altri simboli usati nella definizione sono riprodotti senza alcuna modifica (per esempio il punto e virgola di fine istruzione viene messo solo se compare anche nella definizione).

Nella chiamata di una macro si possono mettere, al posto degli argomenti, anche delle espressioni (come nelle chiamate di funzioni); sarà compito, come al solito, del compilatore controllare che l'espressione risultante sia accettabile. Riprendendo l'esempio precedente, la seguente chiamata:
                     Max(x+1,y)      espansa in      x+1 > y ? x+1 : y
sarà accettata dal compilatore, in istruzioni del tipo :
                     c = Max(x+1,y);
ma rigettata in istruzioni come:
                     Max(x+1,y) = c;
in quanto, in questo caso, gli operandi di un operatore condizionale devono essere l-values.

In altri casi, la sostituzione "cieca" può causare errori che lo stesso compilatore non é in grado di riconoscere.
Es.               #define  quadrato(x)   x*x
la chiamata:    quadrato(2+3)   viene espansa in   2+3*2+3   con risultato, evidentemente, errato.
Per evitare tale errore si sarebbe dovuto scrivere:     #define  quadrato(x)   (x)*(x)

Agli effetti pratici (purché si usino le dovute attenzioni!), la definizione di una macro produce gli stessi risultati dello specificatore inline di una funzione.

 


 

Confronto fra la direttiva #define e lo specificatore inline

 

Vantaggi nell'uso di inline:

Vantaggi nell'uso di #define:

Le macro, usatissime in C, sono raramente utilizzate in C++, se non per funzioni molto brevi e adoperate a livello locale (cioè nello stesso modulo in cui sono definite). Un uso più frequente delle macro si ha quando non corrispondono a funzioni ma a espressioni "parametrizzate" molto lunghe che compaiono più volte nel programma.

 


 

Direttive condizionali

 

Il preprocessore dispone di un suo mini-linguaggio di controllo, che consiste nelle seguenti direttive condizionali:

#if espressione1      oppure     #if defined(identificatore1)    oppure ...
#if !defined(identificatore1)

...... blocco di direttive e/o istruzioni ........

#elif espressione2      oppure #elif defined(identificatore2)   oppure ...
#elif !defined(identificatore2)

...... blocco di direttive e/o istruzioni ........

#else

...... blocco di direttive e/o istruzioni ........

#endif

                dove:      

espressione é un espressione logica che può contenere solo identificatori di costanti predefinite o costanti literals, ma non variabili e neppure variabili dichiarate const, che il preprocessore non riconosce

defined(identificatore) restituisce vero se identificatore é definito (cioè se é stata eseguita la direttiva: #define identificatore); al posto di #if defined(identificatore) si può usare la forma: #ifdef identificatore

!defined(identificatore) restituisce vero se identificatore non é definito; al posto di #if !defined(identificatore) si può usare la forma: #ifndef identificatore

#elif sta per else if ed é opzionale (possono esserci più blocchi consecutivi, ciascuno introdotto da un #elif)

#else é opzionale (se esiste, deve introdurre l'ultimo blocco prima di #endif)

#endif (obbligatorio) termina la sequenza iniziata con un #if

non é necessario racchiudere i blocchi fra parentesi graffe, perché ogni blocco é terminato da #elif, o da #else, o da #endif

Il preprocessore identifica il blocco (se esiste) che corrisponde alla prima condizione risultata vera, oppure il blocco relativo alla direttiva #else (se esiste) nel caso che tutte le condizioni precedenti siano risultate false. Tale blocco può contenere sia istruzioni di programma che altre direttive, comprese direttive condizionali (possono esistere più blocchi #if "innestati"): il preprocessore esegue le direttive e presenta al compilatore le istruzioni che si trovano nel blocco selezionato, scartando sia direttive che istruzioni contenute negli altri blocchi della sequenza #if ... #endif.

 


 

Direttiva #undef

 

La direttiva:

#undef   identificatore

indica al preprocessore di disattivare l'identificatore specificato, cioè rimuovere la corrispondenza fra l'identificatore e una costante, precedentemente stabilita con la direttiva:

#define identificatore costante

Nelle istruzioni successive alla direttiva #undef, lo stesso nome potrà essere adibito ad altri usi.
Es.           #ifdef EOF
#undef EOF
#endif
char EOF[] = "Ente Opere Filantropiche";

 


 

[p25]

Torna all'Indice