[>a2212.html>] [<a2210.html<] [^a2.html^]
Il preprocessore è un programma, o quella parte del compilatore, che si occupa di pre-elaborare un sorgente prima della compilazione vera e propria. In pratica, permette di generare un nuovo sorgente prima che questo venga compilato effettivamente. L'utilità della presenza di un preprocessore, tra le altre cose, sta nella possibilità di definire gruppi di istruzioni alternativi a seconda di circostanze determinate.
Il linguaggio C non può fare a meno della presenza di un preprocessore, e anche le sue direttive sono state regolate con lo standard ANSI.
Le direttive del preprocessore rappresentano un linguaggio a sé stante, con le sue regole particolari. In generale:
le direttive iniziano con il simbolo #, preferibilmente nella prima colonna;
le direttive non utilizzano alcun simbolo di conclusione (non si usa il punto e virgola);
una riga non può contenere più di una direttiva.
Nelle sezioni seguenti vengono descritte le direttive più importanti.
#include <file>
#include "file"
La direttiva #include permette di includere un file. Generalmente si tratta di un cosiddetto file di intestazione, contenente una serie di definizioni necessarie al file sorgente in cui vengono incorporate.
Come si vede dalla sintassi, il file può essere indicato delimitandolo con le parentesi angolari, oppure con gli apici doppi.
#include <stdio.h>
#include "stdio.h"
Nel primo caso si fa riferimento a un file che dovrebbe trovarsi in una posizione stabilita dalla configurazione del compilatore (nel caso del C GNU in GNU/Linux, dovrebbe trattarsi della directory /usr/include/
); nel secondo si fa riferimento a una posizione precisa, che richiede l'indicazione di un percorso se non si tratta della stessa posizione in cui si trova il sorgente in questione.
Un file incorporato attraverso la direttiva #include, può a sua volta includerne altri, e questa possibilità va considerata per evitare di includere più volte lo stesso file.
#define macro [sequenza_di_caratteri]
La direttiva #define permette di definire dei nomi, conosciuti come macro, oppure «costanti simboliche» o «costanti manifeste». Quando queste macro vengono utilizzate nel sorgente, sono sostituite automaticamente con la sequenza che appare dopo la loro definizione. Per esempio,
#define SALUTO ciao come stai
farà in modo che il preprocessore sostituisca tutte le occorrenze di SALUTO con ciao come stai. È molto importante comprendere questo particolare: tutto ciò che appare dopo il nome della macro sarà utilizzato nella sostituzione. Per esempio,
#define SALUTO "ciao come stai"
è diverso dal caso precedente, perché ci sono in più gli apici doppi. Questa volta, la macro SALUTO potrebbe essere utilizzata in un'istruzione come quella seguente,
printf (SALUTO);
mentre non sarebbe stato possibile quando la sostituzione era stata definita senza apici.
Visto questo, si può osservare che questa direttiva può essere utilizzata in modo più complesso, facendo anche riferimento ad altre macro già definite.
#define UNO 1 #define DUE UNO+UNO #define TRE DUE+UNO
In presenza di una situazione come questa, utilizzando la macro TRE, si ottiene prima la sostituzione con DUE+UNO, quindi con UNO+UNO+1, e infine con 1+1+1 (dopo, tocca al compilatore).
L'utilizzo tipico delle macro è quello con cui si definiscono le dimensioni di qualcosa, per esempio gli array, e la corrispondenza reale di valori determinati che dipendono dalla piattaforma.
Per convenzione, i nomi utilizzati per le macro sono espressi solo con lettere maiuscole. |
Come si vedrà meglio in seguito, è sensato anche dichiarare una macro senza alcuna corrispondenza. Ciò può servire per le direttive #ifdef e #ifndef. |
#define macro(argomento) sequenza_di_caratteri
La direttiva #define può essere usata in modo simile a una funzione, per definire delle sostituzioni che includono in qualche modo un argomento. Seguendo l'esempio seguente, l'istruzione i = DOPPIO(i) si traduce in i = (i)+(i).
#define DOPPIO(a) (a)+(a) ... ... i = DOPPIO(i); ...
Si osservi il fatto che, nella definizione, la stringa di sostituzione è stata composta utilizzando le parentesi. Questo permette di evitare problemi successivamente, nelle precedenze di valutazione delle espressioni.
#if espressione espressione endif
Le direttive #if, #else, #elif e #endif, permettono di delimitare una porzione di codice che debba essere utilizzato o ignorato in relazione a una certa espressione che può essere calcolata solo attraverso definizioni precedenti.
#define DIM_MAX 1000 ... ... main () { ... #if DIM_MAX>100 printf ("Dimensione enorme."); ... #else printf ("Dimensione normale."); ... #endif ... }
L'esempio mostra in che modo si possa definire questa espressione, confrontando la macro DIM_MAX con il valore 100. Essendo stata dichiarata per tradursi in 1 000, il confronto è equivalente a 1 000 > 100 che risulta vero, pertanto il compilatore include solo le istruzioni relative.
In particolare, l'istruzione #elif, come si può intuire, serve per costruire una catena di alternative else-if.
Gli operatori di confronto che si possono utilizzare per le espressioni logiche sono i soliti, in particolare, è bene ricordare che per valutare l'uguaglianza si usa l'operatore ==.
#define NAZIONE ita ... ... main () { #if NAZIONE==ita char valuta[] = "LIT"; ... #elsif NAZIONE==usa char valuta[] = "USD"; ... #endif ... }
Queste direttive condizionali possono essere annidate; inoltre possono contenere anche altri tipi di direttiva del preprocessore.
Le direttive #ifdef e #ifndef si aggiungono a quelle descritte nella sezione precedente; servono per definire l'inclusione o l'esclusione di codice in base all'esistenza o meno di una macro.
#define DEBUG ... main () { ... #ifdef DEBUG printf ("Punto di controllo n. 1\n"); ... #endif ... }
L'esempio mostra il caso in cui sia dichiarata una macro DEBUG (che non si traduce in alcunché) e in base alla sua esistenza viene incluso il codice che mostra un messaggio particolare.
#define OK ... main () { #ifndef OK printf ("Punto di controllo n. 1\n"); ... #endif ... }
L'esempio appena mostrato è analogo a quello precedente, con la differenza che la direttiva #ifndef permette la compilazione delle istruzioni che controlla solo se la macro indicata non è stata dichiarata.
L'uso delle direttive #else e #endif avviene nel modo già visto per la direttiva #if.
#undef macro
La direttiva #undef permette di eliminare una macro a un certo punto del sorgente.
#define NAZIONE ita ... /* In questa posizione, NAZIONE risulta definita */ ... #undef NAZIONE ... /* In questa posizione, NAZIONE non è definita */ ...
Il compilatore C ANSI prevede alcune macro predefinite. Il loro scopo è quello di annotare informazioni legate alla compilazione nel file eseguibile finale (evidentemente a fini diagnostici).
Il programma può accedere all'informazione sul nome del file sorgente e della riga originale. Questi dati sono contenuti, rispettivamente, nelle macro __FILE__ e __LINE__. Questi dati possono essere alterati nel sorgente, utilizzando la direttiva #line.
#line numero_riga "nome_file"
La data e l'ora della compilazione sono accessibili attraverso le macro __DATE__ e __TIME__. Il formato della prima macro è la consueta stringa «mese/giorno/anno» e quello della seconda è «ore:minuti:secondi».
Se il compilatore C che si utilizza è «standard», allora la macro __STDC__ corrisponde al valore 1. Qualunque altro valore indica che non si tratta di un compilatore standard.
Appunti di informatica libera 2000.07.31 --- Copyright © 2000 Daniele Giacomini -- daniele @ swlibero.org1) Quando si indica un file da includere, delimitandolo con gli apici doppi e senza indicare alcun percorso, se non si trova il file nella directory corrente, il file viene cercato nella directory predefinita, come se fosse stato indicato tra le parentesi angolari.