Il preprocessore C

28.1 I file header

I file header (file testata) sono dei file che racchiudono informazioni necessarie al programma e vengono inclusi nel sorgente dal preprocessore. Il preprocessore e' un programma che fa parte del compilatore che effettua una elaborazione preliminare del sorgente. Il preprocessore (o precompilatore) modifica il sorgente seguendo alcune direttive scritte dal programmatore. Il file sorgente cosi' modificato viene successivamente elaborato dal compilatore vero e proprio. Le direttive del precompilatore sono evidenziate dal carattere '#' iniziale (il simbolo '#' viene chiamato pound negli USA, hash fuori degli USA e cancelletto in Italia. Etimologicamente parlando probabilmente il termine hash deriva da 'hatch' cioe' portellone, cancello, porta composta da sbarre incrociate cosi' come raffigurato dal simbolo stesso). Una direttiva importante che viene impartita al preprocessore e' la direttiva #include. Tale direttiva permette di includere i file header all'interno del sorgente del programma. I file header sono file di testo con estensione '.h' (da Header) e contengono le definizioni delle variabili, delle espressioni costanti e dei prototipi delle funzioni. Ad esempio, nel file header 'stdio.h' (STandarD Input/Output) sono contenuti le definizioni delle variabili, delle espressioni costanti ed i prototipi delle funzioni di Input e di Output della libreria standard del C. Esempi di funzioni I/O stantdard del C sono le funzioni printf(), getchar() e putchar(). Se il file stdio.h non venisse incluso all'interno del programma, il compilatore non riconoscerebbe le funzioni standard tipo printf() e genererebbe un errore di funzione non definita. In realta' generalmente le cose non stanno cosi' poiche' il file stdio.h se non incluso dal programmatore viene spesso incluso di default dal compilatore stesso. Il preprocessore leggendo la direttiva #include copia (o meglio lo 'include') il file dichiarato all'interno del sorgente del programma. Una volta elaborato dal preprocessore quindi, il file sorgente conterra' al posto della riga '#include ' il file stdio.h stesso (percio' il file sorgente iniziale aumentera' di dimensioni). Il file cosi' modificato verra' poi elaborato dal compilatore stesso. Per default se si usano i simboli '<' e '>' il compilatore prelevera' i file '.h' da una directory include ben precisa (generalmente /usr/include ma dipende dall'implementazione del compilatore). Ad ogni modo e' possibile usare gli apici doppi al posto dei simboli '<' e '>': in questo caso i file header vengono cercati prima di tutto nella directory corrente. Cio' puo' essere utile se si vuole ad esempio creare un proprio file header. Per specificare una directory diversa si possono usare i doppi apici specificando il path ad esempio: #inlcude "/home/mau/c/include/miofile.h". Le direttive impartite al preprocessore non terminano con il punto e virgola (inserendo un punto e virgola dopo una direttiva impartita al preprocessore verra' generato un errore).

28.2 la direttiva #define e le macro

Un'altra direttiva molto usata e' #define per creare delle espressioni costanti. Una espressione costante permette di usare un nome significativo al posto di un valore costante come ad esempio 'ON' (acceso) al posto di 1 e 'OFF' (spento) al posto di 0. La sintassi della direttiva #define e': '#define nome espressione'. Il preprocessore quando incontra la direttiva #define sostituisce qualsiasi occorrenza identificata da 'nome' con l'espressione 'espressione'. E' possibile scrivere direttive #define su piu' righe usando il carattere '\' alla fine di ogni riga. Questa direttiva pero' puo' essere usata anche per creare delle macro. Ecco alcuni esempi di macro create utilizzando la direttiva #define:

#define forever for(;;)   /*  ciclo infinito   */
#define trovato 1
#define nontrovato 0
#define UNO 1
#define MILLE 1000
#define max(a,b) ((a) > (b) ? (a) : (b))

l'ultima direttiva sostituisce qualsiasi occorrenza di max(a,b) con l'espressione ((a) > (b) ? (a) : (b)). Ad esempio la riga:

x = max(p q, r s);

viene sostituita con:

x = ((p q) > (r s) ? (p q) : (r s));

Nella definizione delle macro occorre prestare attenzione alle parentesi onde evitare spiacevoli effetti collaterali. Ad esempio la macro:

#define quad(x) x*x      /*  errata  */

e' errata perche' quando viene incontrata un'espressione del tipo quad(x 1) viene espansa nellla forma: (x 1*x 1). Poiche' l'operatore * ha una precedenza maggiore dell'operatore , verrebbe eseguita per prima la moltiplicazione. Sostituendo x con un valore qualsiasi, ad esempio 3, si avrebbe: (3 1*3 1), cioe' 3 3 1 ossia 7. In realta' l'espressione espansa correttamente sarebbe dovuta essere: (x 1)(x 1), cioe' (3 1)(3 1) ossia 16. Percio' per produrre il risultato corretto occorre definire la macro in questo modo:

#define quad(x) ((x)*(x))

In sostanza non devono esserci spazi fra la parentesi prima degli argomenti ed il nome della macro ed inoltre sono necessarie le parentesi aggiuntive nel testo della macro. Nel file header stdio.h le funzioni getchar e putchar sono definite come macro, per evitare di dover effettuare una chiamata a funzione ad ogni lettura di un carattere. Infatti usare le macro e' piu' veloce delle chiamate a funzioni, ma costa in termini di memoria poiche' il codice della macro e' espanso nel programma ogni volta che viene incontrata una occorrenza che soddisfa la definizione data all'interno della macro. Le funzioni definite come macro possono avere degli argomenti preceduto dal simbolo #: in questi casi tale carattere viene trasformato in una stringa. Ad esempio:

funz(valore) = printf ("valore" " = %d", valore);

viene espansa come: 'printf ("valore = %d",valore);'.

28.3 Le macro predefinite ed altre direttive

Esistono poi delle macro predefinite che permettono di inserire informazioni aggiuntive nel file eseguibile finale come ad esempio il nome del programma, la data e l'ora di compilazione. Le macro predefinite indicate dallo standard sono:

  • __LINE__
  • __FILE__
  • __DATE__
  • __TIME__
  • __STDC__
  • __STDC_VERSION__
  • __STDC_ISO_10646__
  • __STDC_IEC_559__
  • __STDC_IEC_559_COMPLEX__
Utilizzando tali macro predefinite, all'interno di un programma e' possibile far riferimento: al numero di riga corrente, al nome del file che contiene il sorgente del programma, alla data di compilazione, all'ora, allo standard usato. E' possibile modificare quanto indicato dalla macro __LINE__ mediante la direttiva #line. Con tale direttiva e' possibile impostare un numero di riga arbitrario. Esistono tre formati:
  • #line numero
  • #line numero "nomefile"
  • #line qualsiasicosa
Ad esempio:
int main (void)                                      /* RIGA 1: il programma inizia a riga 1                         */
{                                                    /* RIGA 2                                                       */
  printf("riga %d nel file %s", __LINE__, __FILE__); /* RIGA 3: la printf() stampa 3 come numero di riga             */
  #line 1                                            /* RIGA 4: il numero di riga viene reimpostato a 1              */
  printf("riga %d nel file %s", __LINE__, __FILE__); /* RIGA 5: la printf() stampa 1 invece di 5 come numero di riga */
}

oppure:

int main(void)
{
   if ( (fp = fopen(miofile,"r")) == NULL )
   {
      printf ("open fallita, riga numero: %d \n %s \n", __LINE__, __FILE__);
      exit (4);
   }
}

Nell'ultimo esempio viene stampato un messaggio di errore ed il numero di riga dove tale errore si e' verificato (in questo caso una apertura del file miofile non andata a buon fine). Un'altra direttiva che e' possibile impartire al preprocessore e' la direttiva #error. La sintassi e' la seguente:

#error messaggio_errore    /*      (attenzione: il messaggio non deve essere racchiuso tra apici)    */

quest'istruzione sospende momentaneamente la compilazione per mostrare il messaggio di errore specificato (ed altre informazioni che variano da compilatore a compilatore). Il messaggio non deve essere specificato tra apici. La direttiva #pragma cambia a seconda dell'implemetazione e premette l'esecuzione di istruzioni speciali del compilatore definite dal produttore (a tale scopo consultare la documentazione del compilatore usato).

28.4 Le direttive condizionali

E' possibile impartire le direttive al preprocessore in modo condizionato, utilizzando i seguenti comandi:

  • #if espressione
  • #else espressione
  • #elif espressione
  • #endif espressione
  • #ifdef espressione
  • #ifndef espressione
  • defined (espressione)
  • #undef espressione
utilizzando queste direttive e' possibile ad esempio evitare di includere uno stesso file piu' volte, oppure delle istruzioni solo in alcuni casi. La direttiva #ifdef e' equivalente a '#if defined (espressione)' mentre la direttiva #ifndef e' equivalente a '#if !defined (espressione)'. La penultima riga non contiene il simbolo '#', infatti 'defined' e' un operatore e non una direttiva. Esempi:
#define NAZIONE usa
...
...
main() {
#if NAZIONE==ita
    char valuta[] = "EUR";
    ...
#elsif NAZIONE==usa
    char valuta[] = "USD";
    ...
#endif
...
}

oppure:

#if SYSTEM == MSDOS
    #include 
#else
    #include "default.h"
#endif

oppure:

#if  MACRO==0
   char  ch[10]="MACRO";
#elif  MACRO==1
   char ch[10]="no_macro";          /* The #elif group */
#endif

oppure:

#ifndef PIPPO
   #define PIPPO 1
#endif

oppure:

#if !defined (PIPPO)
    #define PIPPO 1
#endif

L'operatore ## e' utilizzato per concatenare due stringhe, ad esempio: #define concat(a,b)(a)##(b). Infine, la direttiva #undef cancella una precedente definizione di macro.

Inizio della guida  Costrutti di controllo  Indice  I puntatori

Copyright (c) 2002-2003 Maurizio Silvestri