- Programmazione » Programmazione » Guida C - Manuale programmazione con articoli e risorse interessanti
Le costanti manifeste
LE COSTANTI
Supponiamo di scrivere un programma per la gestione dei conti correnti bancari. E' noto (e se non lo era ve lo dico io) che nei calcoli finanziari la durata dell'anno è assunta pari a 360 giorni. Nel sorgente del programma si potrebbero perciò incontrare calcoli come il seguente:
interesse = importo * giorniDeposito * tassoUnitario / 360;
il quale impiega, quale divisore, la costante intera
360.
E' verosimile che nel programma la costante 360
compaia più volte, in diversi contesti (principalmente
in formule di calcolo finanziario). Se in futuro fosse
necessario modificare il valore della costante (una nuova
normativa legale potrebbe imporre di assumere la durata
dell'anno finanziario pari a 365 giorni) dovremmo
ricercare tutte le occorrenze della costante 360 ed
effettuare la sostituzione con 365. In un sorgente
di poche righe tutto ciò non rappresenterebbe certo un
guaio, ma immaginando un codice di diverse migliaia di righe
suddivise in un certo numero di file sorgenti, con qualche
centinaio di occorrenze della costante, è facile
prevedere quanto gravoso potrebbe rivelarsi il compito, e
quanto grande sarebbe la possibilità di non riuscire a
portarlo a termine senza errori.
Il preprocessore C consente di aggirare l'ostacolo
mediante la direttiva #define, che associa tra loro
due sequenze di caratteri in modo tale che, prima della
compilazione ed in modo del tutto automatico, ad ogni
occorrenza della prima (detta manifest constant)
è sostituita la seconda.
Il nome della costante manifesta ha inizio col primo
carattere non blank[31] che segue la direttiva
#define e termina con il carattere che precede il
primo successivo nonspazio; tutto quanto segue
quest'ultimo è considerato stringa di
sostituzione.
Complicato? Solo in apparenza...
#define GG_ANNO_FIN 360 //durata in giorni dell'anno finanziario
....
interesse = importo * giorniDeposito * tassoUnitario / GG_ANNO_FIN;
L'esempio appena visto risolve il nostro problema:
modificando la direttiva #define in modo che al
posto del 360 compaia il 365 e ricompilando
il programma, la sostituzione viene effettuata
automaticamente in tutte le righe in cui compare
GG_ANNO_FIN.
Va sottolineato che la direttiva #define non crea
una variabile, né è associata ad un tipo di
dato particolare: essa informa semplicemente il preprocessore
che la costante manifesta, ogniqualvolta compaia nel sorgente
in fase di compilazione, deve essere rimpiazzata con la
stringa di sostituzione. Gli esempi che seguono forniscono
ulteriori chiarimenti: in essi sono definite costanti
manifeste che rappresentano, rispettivamente, una costante
stringa, una costante in virgola mobile, un carattere
esadecimale e una costante long integer, ancora
esadecimale.
#define NOME_PROG "Conto 1.0" //nome del programma #define PI_GRECO 3.14 //pi greco arrotondato #define RETURN 0x0D //ritorno a capo #define VIDEO_ADDRESS 0xB8000000L //indirizzo del buffer video
Le costanti manifeste possono essere definite utilizzando altre costanti manifeste, purché definite in precedenza:
#define N_PAG_VIDEO 8 //numero di pagine video disponibili #define DIM_PAG_VIDEO 4000 //4000 bytes in ogni pagina video #define VIDEO_MEMORY (N_PAG_VIDEO * DIM_PAG_VIDEO) //spazio memoria video
Una direttiva #define può essere suddivisa in più righe fisiche mediante l'uso della backslash:
#define VIDEO_MEMORY
(N_PAG_VIDEO * DIM_PAG_VIDEO)
L'uso delle maiuscole nelle costanti manifeste non
è obbligatorio; esso tuttavia è assai diffuso
in quanto consente di individuarle più facilmente
nella lettura dei sorgenti.
Come tutte le direttive al preprocessore, anche la
#define non si chiude mai con il punto e virgola (un
eventuale punto e virgola verrebbe inesorabilmente
considerato parte della stringa di sostituzione); inoltre il
crosshatch ("#" , cancelletto) deve
trovarsi in prima colonna.
La direttiva #define, implementando una vera e
propria tecnica di sostituzione degli argomenti, consente di
definire, quali costanti manifeste, vere e proprie formule,
dette macro, indipendenti dai tipi di dato
coinvolti:
#define min(a,b) ((a < b) ? a : b) // macro per il calcolo del minimo tra due
Come si vede, nella macro min(a,b) non è data
alcuna indicazione circa il tipo di a e b:
essa utilizza l'operatore ? :, che può
essere applicato ad ogni tipo di dato[32]. Il
programmatore è perciò libero di utilizzarla in
qualunque contesto.
Le macro costituiscono dunque uno strumento molto potente,
ma anche pericoloso: in primo luogo, la mancanza di controlli
(da parte del compilatore) sui tipi di dato può
impedire che siano segnalate incongruenze logiche di un certo
rilievo (sommare le pere alle mele, come si dice...). In
secondo luogo, le macro prestano il fianco ai cosiddetti
sideeffect, o effetti collaterali. Il C
implementa un particolare operatore, detto di
autoincremento[33], che accresce di una unità il
valore della variabile a cui è anteposto: se applicato
a uno dei parametri coinvolti nella macro, esso viene
applicato più volte al parametro, producendo risultati
indesiderati:
int var1, var2;
int minimo;
....
minimo = min(++var1, var2);
La macrosotituzione effettuata dal preprocessore trasforma l'ultima riga dell'esempio nella seguente:
minimo = ((++var1 < var2) ? ++var1 : var2);
E' facile vedere che esso si limita a sostituire alla
macro min la definizione data con la
#define, sostituendo altresì i parametri
a e b con i simboli utilizzati al loro
posto nella riga di codice, cioè ++var1 e
var2. In tal modo var1 è
incrementata due volte se dopo il primo incremento essa
risulta ancora minore di var2, una sola volta nel
caso opposto. Se min() fosse una funzione il
problema non potrebbe verificarsi (una chiamata a funzione
non è una semplice sostituzione di stringhe, ma
un'operazione tradotta in linguaggio macchina dal
compilatore seguendo precise regole); tuttavia una funzione
non accetterebbe indifferentemente argomenti di vario tipo, e
occorrerebbe definire funzioni diverse per effettuare
confronti, di volta in volta, tra integer, tra floating
point, e così via. Un altro esempio di effetto
collaterale riguarda più da vicino il comportamento
del compilatore.
Diamo un'occhiata all'esempio che segue:
#define PROG_NAME "PROVA"
....
printf(PROG_NAME);
....
#undef PROG_NAME
....
printf(PROG_NAME); // Errore! PROG_NAME non esiste piu'...
Quando una definizione generata con una #define non
serve più, la si può annullare con la direttiva
#undefine. Ogni riferimento alla definizione
annullata, successivamente inserito nel programma, dà
luogo ad una segnalazione di errore da parte del
compilatore.
Da notare che PROG_NAME è passata a
printf() senza porla tra virgolette, in quanto esse
sono già parte della stringa di sostituzione, come si
può vedere nell'esempio. Se si fossero utilizzate
le virgolette, printf() avrebbe scritto
PROG_NAME e non PROVA: il preprocessore,
infatti, ignora tutto quanto è racchiuso tra
virgolette o apici. In altre parole, esso non ficca il naso
nelle costanti stringa e in quelle di tipo
carattere.
Vale la pena di citare anche la direttiva
#ifdef...#else...#endif, che consente di includere o
escludere dalla compilazione un parte di codice, a seconda
che sia, o meno, definita una determinata costante
manifesta:
#define DEBUG .... #ifdef DEBUG .... // questa parte del sorgente e' compilata #else .... // questa no (lo sarebbe se NON fosse definita DEBUG) #endif
La direttiva #ifndef e' analoga alla #ifdef, ma lavora con logica inversa:
#define DEBUG .... #ifndef DEBUG .... // questa parte del sorgente NON e' compilata #else .... // questa si (NON lo sarebbe se NON fosse definita DEBUG) #endif
Le direttive #ifdef e #ifndef risultano particolarmente utili per scrivere codice portabile: le parti di sorgente differenti in dipendenza dal compilatore, dal sistema o dalla macchina possono essere escluse o incluse nella compilazione con la semplice definizione di una costante manifesta in testa al sorgente.