Il preprocessore del linguaggio C
1
Anno accademico 2010-2011
Sommario
• Il preprocessore
 La sostituzione di macro
 Le compilazioni condizionali
 L’inclusione di file
2
Anno accademico 2010-2011
Introduzione  1
• Il preprocessore del linguaggio C è un programma a sé
stante, che viene eseguito prima del compilatore, con propri
lessico e sintassi, orientati alle linee
• Le principali funzioni offerte dal preprocessore sono:
 Elaborazione di macro
 Inclusione di file sorgente
 Compilazione condizionale, che consente di compilare porzio-
ni distinte di codice sorgente in dipendenza del valore di
un’espressione aritmetica
• Tutte le direttive per il preprocessore iniziano con il carattere
diesis,  (il cancelletto), che deve essere il primo carattere
della linea a meno di spazi bianchi
3
Anno accademico 2010-2011
Introduzione  2
• Le direttive del preprocessore possono apparire ovunque nel
codice sorgente (prima, dopo, o inframmezzate a istruzioni C)
• Una definizione di macro termina con un newline invece che
con un punto e virgola
• Per suddividere una definizione di macro su più linee occorre
dunque inserire un backslash, \, immediatamente prima del
newline
• Esempio:
define LONG_MACRO “Questa è una macro molto lunga\
che si estende su due linee”
4
Anno accademico 2010-2011
La sostituzione di macro  1
• Per macro si intende un nome a cui è associata una stringa testuale,
detta corpo della macro
• Per convenzione, i nomi di macro dovrebbero essere costituiti da
sole lettere maiuscole e dovrebbero essere significativi del
contenuto della macro
• Per lo standard ANSI, due nomi di macro sono distinti se
differiscono in almeno uno dei primi 31 caratteri
• Quando un nome di macro viene invocato nel codice al di fuori del
punto di definizione, viene sostituito dal corpo della macro: si ha
una espansione di macro
• L’utilizzo più comune delle macro consiste nella definizione di
costanti numeriche: l’uso diretto di costanti nel codice costituisce
una pratica di programmazione scadente, perché il programma
diventa difficile da (leggere e) manutenere
Anno accademico 2010-2011
5
La sostituzione di macro  2
• Oltre alle macro che definiscono costanti, esiste un’ulteriore
forma di macro, simile ad una funzione C, che accetta
argomenti che possono essere utilizzati nel corpo della
macro
,
• La sintassi è:

define
)
• Esempio:
Corpo di macro
Nome di macro
(
Argomento di
macro
Sintassi di una macro di tipo funzione
define MULT_BY_TWO(a) ((a)(a))
6
Anno accademico 2010-2011
La sostituzione di macro  3
• MULT_BY_TO può essere utilizzata all’interno del programma, come
una funzione: le prestazioni migliorano perché la somma “costa”
meno della moltiplicazione
jMULT_BY_TWO(5);
j10;
Il parametro attuale 5 viene sostituito al parametro formale a, in ogni
sua occorrenza all’interno del corpo della macro
 Le parentesi che delimitano a ed il corpo della macro sono necessarie
per assicurare che il parametro venga istanziato correttamente all’atto
dell’espansione della macro

• I parametri di una macro non sono variabili: non è definito il loro
tipo, né viene loro assegnata memoria  non sono in conflitto con
variabili esistenti con lo stesso nome
7
Anno accademico 2010-2011
La sostituzione di macro  4
• Le macro vengono normalmente eseguite più velocemente
delle funzioni, perché non è necessario il salvataggio degli
argomenti sullo stack
• Esempio: Trasformazione da maiuscole a minuscole, nel caso
di codifica ASCII
define TO_LOWER(c) ((c)(‘a’‘A’))
• La conversione di funzioni in macro comporta effetti
significativi sui tempi di esecuzione del programma quando la
frequenza di attivazione della funzione è elevata
8
Anno accademico 2010-2011
Errori comuni  1
• L’introduzione di un ; al termine di una definizione di macro è un
errore molto diffuso e molto pericoloso
• Esempio:
define size 10;
Il punto e virgola diviene parte integrante della stringa da
espandere, per cui
x  size;
x  10;;
L’errore non viene segnalato dal compilatore, che interpreta il
secondo punto e virgola come un’istruzione vuota
• Viceversa, produce un errore l’espansione di…
int array[size];
La linea a cui viene fatto riferimento
nel messaggio di errore è corretta!
9
Anno accademico 2010-2011
Errori comuni  2
• L’errore più pericoloso si verifica quando, a seguito dell’espansione
della macro, l’istruzione risultante è sintatticamente corretta, ma ha
una semantica non corrispondente alle attese
• Esempio:
define GOOD_CONDITION (var  1);
… … …
while GOOD_CONDITION
foo();
while (var  1);
foo();
Il “;” che segue (var  1) viene interpretato come un’istruzione
vuota, che costituisce il corpo del ciclo while  la chiamata alla
funzione non fa parte del corpo del while e se var coincide con 1 si
produce un ciclo infinito
• I compilatori prevedono un’opzione per eseguire solo il preprocessore,
così da esaminare il codice risultante dopo l’espansione di tutte le
macro
10
Anno accademico 2010-2011
Errori comuni  3
• Altro errore molto diffuso è l’uso dell’operatore di assegnamento
nella definizione di una macro, in analogia all’inizializzazione di
variabili
• L’errore può condurre ad anomalie nel codice di difficile
individuazione
• Esempio:
define MAX  100
produrrebbe…
for(jMAX; j>0; j)
for(j100; j>0; j)
L’assegnamento diviene un’espressione relazionale sintatticamente
corretta, che il compilatore non evidenzia  errore difficile da
rilevare
11
Anno accademico 2010-2011
Errori comuni  4
• La parentesi sinistra che racchiude il parametro (/i) deve
seguire immediatamente il nome della macro, senza la
presenza di spazi bianchi: l’errore viene “normalmente”
segnalato in compilazione
• Esempio: define NEG_A_PLUS_F(a) ((a)f)
produrrebbe…
j  NEG_A_PLUS_F(x);
j  (x)f;
Se invece fosse stato inserito uno spazio bianco, l’espressione viene espansa in
j  (a)(a)f(x);
perfettamente lecita se a è un nome di variabile ed f una
funzione
Anno accademico 2010-2011
12
L’uso del nome di macro nella definizione
• Esempio:
define sqrt(x) ((x<0) ? sqrt(x):sqrt(x))
• Lo standard ANSI stabilisce che, se un nome di macro
compare nella propria definizione, allora non viene espanso:
si evita il problema delle espansioni infinite
• L’espansione della macro sqrt produrrebbe…
y  sqrt(5);
y  ((5<0) ? sqrt(5):sqrt(5));
• Nota: l’uso di un nome di macro, all’interno della propria
definizione, ha senso solo se esiste una funzione con lo stesso
nome
13
Anno accademico 2010-2011
Assenza di controllo per
gli argomenti di macro  1
• Dal punto di vista operativo…
define MULT_BY_TWO(a) ((a)(a))
int mult_by_to(a)
int a;
{
return aa;
}
…l’utilizzo di una macro o di una funzione non produce un
risultato equivalente; infatti:
 Sul parametro della macro non viene eseguito alcun controllo di
tipo; la funzione (nell’esempio), invece, presuppone un
argomento intero e restituisce un valore intero
 Se alla funzione viene passata una costante reale, il compilatore
può comportarsi diversamente, in dipendenza dell’esistenza del
prototipo
 MULT_BY_TO può ricevere un parametro a di tipo qualsiasi
Anno accademico 2010-2011
14
Assenza di controllo per
gli argomenti di macro  2
• L’assenza di controlli di tipo sugli argomenti delle macro
aggiunge flessibilità alla programmazione
• Esempio: define MIN(a,b) ((a)<(b) ? (a) : (b))
•
funziona con a e b sia interi che reali
• Fra funzioni e macro esiste anche una sostanziale differenza sul
controllo del numero dei parametri, tra definizione e
invocazione:
 Nelle funzioni, il compilatore C effettua il controllo solo se esiste un
prototipo; in caso contrario, la chiamata di funzione viene
compilata correttamente, con impredicibilità del comportamento in
fase di esecuzione
 Per le macro, si ha sempre una segnalazione di errore in fase di
compilazione
15
Anno accademico 2010-2011
Gli effetti collaterali
negli argomenti di macro
• Esempio:
a  MIN(b,c);
a  ((b)<c ? (b):(c));
se b<c, b viene incrementato due volte
• Per non incorrere in comportamenti indesiderati, occorre non
utilizzare, nelle chiamate di macro, operatori che implicano
effetti collaterali (operatori di incremento, decremento,
assegnamento, etc.)
16
Anno accademico 2010-2011
Il binding degli argomenti
• L’uso di espressioni in cui le parentesi non siano utilizzate
correttamente, come argomenti di macro, può produrre comportamenti indesiderati, a causa della precedenza degli operatori e del
binding
• Esempio:
define SQUARE(a) aa
Se si passa alla macro un’espressione aritmetica…
j  2SQUARE(34);
j  2  3  4  3  4;
…che assegna il valore 22 a j, piuttosto che il valore corretto 98
 Il corpo e gli argomenti di una macro devono essere sempre
racchiusi fra parentesi
17
Anno accademico 2010-2011
La cancellazione di una definizione
di macro
• La definizione di una macro mantiene la sua validità fino al
termine del file sorgente, o fino a quando non viene
esplicitamente “cancellata”, per mezzo della direttiva undef
• Non è possibile ridefinire una macro senza prima averla
cancellata con l’uso della direttiva apposita, a meno che le
definizioni coincidano
18
Anno accademico 2010-2011
Macro vs Funzioni  1
• Sia macro che funzioni consentono di rappresentare con un
singolo nome (alias) un insieme di operazioni
• Vantaggi
 Le macro sono più veloci, perché non richiedono le operazioni
connesse con le chiamate di funzione (salvataggio del contesto)
 Il numero degli argomenti delle macro è sempre soggetto a
controllo da parte del compilatore
 Non è imposto alcun vincolo sul tipo degli argomenti (la stessa
macro può essere utilizzata su più tipi di dati)
19
Anno accademico 2010-2011
Macro vs Funzioni  2
• Svantaggi
 Gli
argomenti di macro vengono valutati ogni volta che
compaiono all’interno del corpo della macro  possibili effetti
collaterali indesiderati
 Il corpo delle funzioni è compilato una sola volta: molteplici
chiamate alla stessa funzione usufruiscono dello stesso codice
eseguibile; le macro vengono espanse ad ogni occorrenza
 Nelle macro non vi è controllo di tipo sugli argomenti; le funzioni
definite con prototipi controllano sia il numero che il tipo degli
argomenti
 La fase di debugging per programmi contenenti macro è più
complicata: il codice sorgente è sottoposto a due fasi di
traduzione  il codice oggetto è più “distante” dal sorgente
20
Anno accademico 2010-2011
Le compilazioni condizionali  1
• Il preprocessore consente
di selezionare le porzioni di
codice che devono essere
compilate, attraverso le direttive if, else, elif,
endif
• Esempio: if x  1
undef x
define x 0
elif x  2
undef x
define x 3
else
define y 4
endif
Anno accademico 2010-2011
Espressione
condizionale
if

Codice
sorgente C

elif

else
Codice
sorgente C

Espressione
condizionale
La sintassi delle direttive di
compilazione condizionale
endif
21
Le compilazioni condizionali  2
• L’espressione condizionale contenuta in una direttiva if o
elif deve essere una costante, che non deve necessariamente essere racchiusa tra parentesi (sono opzionali)
• La direttiva elif è equivalente al costrutto else if del
linguaggio C
• I blocchi di istruzioni dipendenti da una direttiva condizionale
di preprocessore non sono racchiusi tra parentesi graffe, ma
sono delimitati da un’istruzione elif, else, o endif
• Ogni blocco if può contenere un numero qualsiasi di
blocchi elif, ma un solo blocco else, che deve essere
l’ultimo
• Ogni blocco if deve essere terminato da una direttiva
endif
Anno accademico 2010-2011
22
Le compilazioni condizionali  3
• Inoltre…
 Le
macro che compaiono in un’espressione condizionale
vengono espanse prima della valutazione dell’espressione
 Le direttive condizionali di preprocessore possono essere
innestate (come gli if nel linguaggio C)
 Le istruzioni comprese in blocchi condizionali possono essere
anche istruzioni del linguaggio C
• Le compilazioni condizionali sono particolarmente utili nella
fase di debugging, durante lo sviluppo di un programma, per
attivare o disattivare porzioni di codice
23
Anno accademico 2010-2011
Le compilazioni condizionali  4
• Le direttive if e endif controllano la compilazione delle
istruzioni C racchiuse nel blocco condizionale, non la loro
esecuzione
if
DEBUG
if (exp_debug)
{
… … …
}
endif
24
Anno accademico 2010-2011
Il controllo dell’esistenza
di una macro  1
• Le direttive if e elif consentono la compilazione
condizionale, in dipendenza del valore di un’espressione
aritmetica
• Si può compilare in modo condizionale anche in dipendenza
dell’esistenza o meno di una macro, mediante le direttive
ifdef, ifndef e endif
ifdef TEST
printf(“Questo è un test. \n”);
else
printf(“Questo non è un test. \n”);
endif
Se la macro TEST è definita, viene compilata la prima
printf(), altrimenti viene compilata la seconda
25
Anno accademico 2010-2011
Il controllo dell’esistenza
di una macro  2
• Nella maggior parte dei casi è possibile utilizzare if invece di
ifdef e ifndef, poiché un nome di macro viene espanso a
zero se non è definito
• L’eccezione che richiede l’uso di ifdef e ifndef è costituita
dalle macro definite come zero
• Esempio: Si supponga di definire la macro FALSE come zero…
if !FALSE

define FALSE 0
endif
Soluzione
Anno accademico 2010-2011
FALSE viene comunque ridefinita
se è stata definita come zero, mai
altrimenti
ifndef FALSE

define FALSE 0
elif FALSE

undef FALSE

define FALSE 0
endif
26
L’inclusione di file  1
• La direttiva include può presentarsi in uno dei due
formati:
include <nome_file>
include “nome_file”
…nel primo caso, il preprocessore cerca il file in un insieme di directory
dipendenti dal sistema (ad esempio, in UNIX come in LINUX, i file
standard di include sono contenuti in /usr/include)
 …nel secondo caso, il preprocessore cerca il file secondo le usuali
regole di ricerca per lo specifico sistema operativo (tipicamente la
ricerca viene effettuata nella directory corrente); se la ricerca fallisce,
si procede come nel primo caso

• Il comando include consente di includere file di definizione comuni, i file header, che possono essere condivisi da
più file sorgente
27
Anno accademico 2010-2011
L’inclusione di file  2
• I file header sono caratterizzati dall’estensione .h e contengono i
prototipi di funzione, le definizioni delle strutture dati, delle macro e
dei dati globali, necessari alla comunicazione fra moduli
• Scopo dei file header è quello di sintetizzare in un unico file le
informazioni comuni, invece di replicarle in ogni file sorgente
 si semplifica la programmazione e la manutenzione del codice
• Molti sistemi operativi (UNIX, LINUX) forniscono file header
contenenti la definizione di strutture interne del SO
• Anche le librerie di runtime prevedono un insieme di file header
che occorre includere per poter richiamare le relative funzioni
28
Anno accademico 2010-2011
Scarica

Lezione14_1011