Il preprocessore C
Ver. 2.4
© 2010 - Claudio Fornaro - Corso di programmazione in C
Funzionalità
Modifica il codice C prima che venga eseguita la traduzione vera a propria
Le direttive al preprocessore riguardano:
inclusione di file (#include)
definizione di simboli (#define)
sostituzione di simboli (#define)
compilazione condizionale (#if)
macroistruzioni con parametri (#define)
Non essendo istruzioni C non richiedono il ‘;’ finale
3
Inclusione di file
La riga con #include viene sostituita dal contenuto testuale del file indicato
#include <stdio.h>
Il nome del file può essere completo di percorso
Ha due forme:
#include <file.h>
Il file viene cercato nelle directory del compilatore
#include "file.h"
Il file viene cercato primanella directory dove si trova il file C e poi, se non trovato, nelle directory del compilatore
4
Inclusione di file
I file inclusi possono a loro volta contenere altre direttive #include
La direttiva #include viene in genere collocata in testa al file sorgente C, prima della prima funzione
Generalmente, i file inclusi non contengono codice eseguibile, ma solo dichiarazioni (es.
prototipi e variabili extern) e altre direttive
Definizione di simboli
#define nome
definisce il simbolo denominato nome
#define DEBUG
I simboli vengono utilizzati dalle altre direttive del preprocessore (ad es. si può verificare se un simbolo è stato definito o no con #if)
Lo scope di nome si estende dalla riga con la definizione fino alla fine di quel file sorgente e non tiene conto dei blocchi di codice
nome ha la stessa sintassi di un identificatore (nome di variabile), non può contenere spazi e viene per convenzione scritto in maiuscolo
#undef nome
annulla una #define precedente
Sostituzione di simboli
Sono dette anche macro o macroistruzioni
#define nome testo_sostitutivo
sostituisce i caratteri di nome con i caratteri di testo_sostitutivo in ogni punto del programma dove nome appare come identificatore (ossia sembra una variabile)
#define MAX 100
...
for (i=0; i<MAX; i++)
La sostituzione (detta anche “espansione”) avviene prima della compilazione vera e propria, quindi il compilatore non troverà la riga di codice precedente, ma la seguente:
for (i=0; i<100; i++)
7
Sostituzione di simboli
nome non può essere definito più volte
nome ha la sintassi degli identificatori, viene per convenzione scritto tutto maiuscolo se testo_sostitutivo è una quantità costante (ossia non si tratta di una macro con argomenti )
testo_sostitutivo termina a fine riga:
può contenere spazi
può utilizzare nomi di #define precedenti
se manca, si ha la semplice definizione di nome e quindi tutte le occorrenze di nome sono eliminate
può continuare su più righe purché ogni riga da continuare termini con il carattere ‘\’ (dal punto di vista logico si tratta di una sola lunga riga)
8
Sostituzione di simboli
L’ordine di elencazione delle #define non è rilevante: dopo una sostituzione su una riga, questa viene considerata nuovamente per verificare se quanto ottenuto può essere sottoposto ad un’altra sostituzione
#define C 3
#define B C
#define A B
trasforma ogni A in B, ogni B in C e ogni C in 3, quindi tutte le A, le B e le C in 3
Se un nome è stato sostituito da un’espansione
e compare nuovamente per effetto di altre
espansioni, non viene sostituito nuovamente
Sostituzione di simboli
Attenzione: essendo una semplice sostituzione di caratteri , si può incorrere in errori
Esempio: il seguente ciclo non va fino a 202
#define MAX 100+1 ...
for (i=0; i<MAX*2; i++)
In realtà va fino a 102 in quanto la sostituzione produce il seguente codice:
for (i=0; i<100+1*2; i++)
Per evitare il problema a priori, è possibile indicare il valore tra parentesi nella define:
#define MAX (100+1)
Macro con argomenti
Sembra una chiamata di funzione con
parametri, invece è un’espansione di macro:
esecuzione più veloce, programma più grande
#define max(A,B) ((A)>(B)?(A):(B))
I simboli A e B sono sostituiti dagli argomenti presenti nella “chiamata” della macro
La prima parte della #define non può avere spazi intermedi, nell’utilizzo possono esserci
Esempio
x = max(p+q, r+s); viene espansa in x = ((p+q)>(r+s)?(p+q):(r+s));
Si noti che funziona per tutti i tipi di dati
11
Macro con argomenti
Attenzione agli effetti collaterali:
max(i++,j++) viene sostituita da ((i++)>(j++)?(i++):(j++))
e il valore più grande tra i e j viene incrementato due volte
Attenzione alle parentesi:
#define quadrato(x) (x)*(x) chiamata come quadrato(z+1) viene correttamente sostituita da (z+1)*(z+1)
#define quadrato(x) x*x
chiamata come quadrato(z+1) viene erroneamente sostituita da z+1*z+1
12
Macro con argomenti
In testo_sostitutivo il nome di un parametro preceduto da un # viene sostituito
dall’argomento racchiuso tra virgolette
#define eprint(expr) \
printf(#expr "=%g\n", expr) la macro eprint(x/y) viene sostituita da:
printf("x/y" "=%g\n", x/y)
e le due stringhe vicine vengono concatenate dal compilatore in "x/y=%g\n"
L’operatore di preprocessore ## concatena due argomenti rimuovendo gli spazi intermedi
#define concat(uno,due) uno ## due
concat(nome,6) viene sostituito da nome6
Macro composte
Il corpo della macro ( testo_sostitutivo ) può contenere più istruzioni C
#define swap(x,y) t=x; x=y; y=t;
La macro precedente viene chiamata come:
swap(a,b)
Per chiamarla come fosse una funzione void, è sufficiente non indicare all’estrema destra nella definizione il punto e virgola swap(a,b); CON ‘;’
Macro composte
Perché la macro composta restituisca un valore come una funzione, se possibile si separano le istruzioni mediante virgole (dà come valore quello dell’espr. più a destra)
#define maxswap(x,y) \
(t=x, x=y, y=t, x>y?x:y) chiamata come: maxswap(a,b);
Se serve definire una variabile temporanea per la macro, si crea un blocco con una coppia di parentesi graffe:
#define swap(x,y) \
{ int t; t=x; x=y; y=t; } Questa macro deve essere chiamata senza il ‘;’
swap(a,b)
15
Macro composte
Per poter chiamare anche la macro precedente con il ‘;’ a fine istruzione così che sembri una funzione, si scrivono le istruzioni all’interno di un ciclo do-while fittizio (sempre falso, non lo ripete mai) e privo del ‘;’ finale:
#define swap(x,y) \
do { \
int t; t=x; x=y; y=t; \ }while (0)
Questa macro deve essere chiamata con il ‘;’
finale:
swap(a,b);
16
Inclusione condizionale
Permette di include o escludere parte del codice dalla compilazione e dal preprocessing stesso
#if espressione_1 istruzioni
#elif espressione_2 istruzioni
...
#else istruzioni
#endif
Solo uno dei gruppi di istruzioni sarà elaborato
dal preprocessore e poi compilato
Inclusione condizionale
Le espressioni devono essere costanti intere (non possono contenere sizeof(), cast o costanti enum), sono considerate vere se !=0
L’espressione defined(nome) produce 1 se nome è stato definito (con #define), 0 altrimenti
#ifdef nome equivale a:
#if defined(nome)
e verifica che nome sia definito
#ifndef nome equivale a:
#if !defined(nome)
e verifica che nome non sia definito
Inclusione condizionale
Esempio
Per far compilare parti diverse a seconda del sistema operativo, si può usare lo schema seguente (il simbolo _WIN32 viene definito da tutti i compilatori Win32):
#ifdef _WIN32
if (_oneexit(funz)==NULL) abort();
#else
atexit(funz);
#endif
Sono due funzioni quasi equivalenti, la prima esiste solo sui sistemi Win32, la seconda è standard
19
Inclusione condizionale
Nel caso in cui un file incluso ne includa a sua volta altri, per evitare di includere più volte lo stesso file, si può usare lo schema seguente (quello che segue è il file hdr.h):
#ifndef HDR
#define HDR
... contenuto di <hdr.h>
#endif
Se venisse incluso una seconda volta, il simbolo HDR sarebbe già definito e il
contenuto non verrebbe nuovamente incluso nella compilazione
20
Inclusione condizionale
Per escludere dalla compilazione un grosso blocco di codice (anche con commenti):
#if 0
codice da non eseguire
#endif
Per isolare istruzioni da usare solo per il debug:
#ifdef DEBUG
printf("Valore di x: %d\n", x);
#endif
La #define DEBUG viene eliminata a
programma ultimato (spesso il simbolo DEBUG
viene definito dal compilatore stesso se in
configurazione debug)
Macro predefinite
Sono simboli definiti dal preprocessore:
__DATE__Data di compilazione del file sorgente nel formato di asctime() (in <time.h>) __FILE__Nome del file corrente tra virgolette
__LINE__Numero di linea della riga corrente nel file sorgente, può essere alterato da #line __STDC__Indica che il compilatore è completamente
aderente allo standard ANSI e non fornisce alcuna estensione
__TIME__Ora di compilazione del file sorgente nella forma hh:mm:ss.
__TIMESTAMP__Data e ora di compilazione del file
Macro predefinite
La direttiva #line può essere seguita da un numero intero per impostare il valore di
__LINE__ della riga corrente del codice C, la numerazione delle righe successive prosegue da quel valore
#line 100
La direttiva #line può inoltre impostare un nuovo nome di file
#line 100 "file1.c"
Il valore di __LINE__ e il nome del file vengono usati nelle informazioni di diagnostica fornite dal compilatore
23
Esercizi
1.
Scrivere una macro swap(t,x,y) che scambi il valore di due argomenti di tipo t.
2.
Scrivere una macro printArray(v,n) che visualizzi il contenuto del vettore v di
lunghezza n.
3.