Barninga Z
a- a+

Le costanti manifeste

LE COSTANTI

Le costanti manifeste 

Supponiamo di scrivere un programma per la gestione dei conticorrenti 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 sipotrebbero perciò incontrare calcoli come ilseguente: 

interesse = importo * giorniDeposito * tassoUnitario / 360;

il quale impiega, quale divisore, la costante intera360

E' verosimile che nel programma la costante 360compaia più volte, in diversi contesti (principalmentein formule di calcolo finanziario). Se in futuro fossenecessario modificare il valore della costante (una nuovanormativa legale potrebbe imporre di assumere la duratadell'anno finanziario pari a 365 giorni) dovremmoricercare tutte le occorrenze della costante 360 edeffettuare la sostituzione con 365. In un sorgentedi poche righe tutto ciò non rappresenterebbe certo unguaio, ma immaginando un codice di diverse migliaia di righesuddivise in un certo numero di file sorgenti, con qualchecentinaio di occorrenze della costante, è facileprevedere quanto gravoso potrebbe rivelarsi il compito, equanto grande sarebbe la possibilità di non riuscire aportarlo a termine senza errori. 

Il preprocessore C consente di aggirare l'ostacolomediante la direttiva #define, che associa tra lorodue sequenze di caratteri in modo tale che, prima dellacompilazione ed in modo del tutto automatico, ad ognioccorrenza 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 ilprimo successivo non­spazio; tutto quanto seguequest'ultimo è considerato stringa disostituzione. 

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 alposto del 360 compaia il 365 e ricompilandoil programma, la sostituzione viene effettuataautomaticamente in tutte le righe in cui compareGG_ANNO_FIN

Va sottolineato che la direttiva #define non creauna variabile, né è associata ad un tipo didato particolare: essa informa semplicemente il preprocessoreche la costante manifesta, ogniqualvolta compaia nel sorgentein fase di compilazione, deve essere rimpiazzata con lastringa di sostituzione. Gli esempi che seguono fornisconoulteriori chiarimenti: in essi sono definite costantimanifeste che rappresentano, rispettivamente, una costantestringa, una costante in virgola mobile, un carattereesadecimale e una costante long integer, ancoraesadecimale. 

#define    NOME_PROG     "Conto 1.0"   //nome del programma#define    PI_GRECO3.14   //pi greco arrotondato#define    RETURN  0x0D     //ritorno a capo#define    VIDEO_ADDRESS 0xB8000000L //indirizzo del buffer video

Le costanti manifeste possono essere definite utilizzandoaltre costanti manifeste, purché definite inprecedenza: 

#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 inpiù righe fisiche mediante l'uso dellabackslash: 

#define    VIDEO_MEMORY   (N_PAG_VIDEO * DIM_PAG_VIDEO)

L'uso delle maiuscole nelle costanti manifeste nonè obbligatorio; esso tuttavia è assai diffusoin quanto consente di individuarle più facilmentenella lettura dei sorgenti. 

Come tutte le direttive al preprocessore, anche la#define non si chiude mai con il punto e virgola (uneventuale punto e virgola verrebbe inesorabilmenteconsiderato parte della stringa di sostituzione); inoltre ilcrosshatch ("#" , cancelletto) devetrovarsi in prima colonna. 

La direttiva #define, implementando una vera epropria tecnica di sostituzione degli argomenti, consente didefinire, quali costanti manifeste, vere e proprie formule,dette macro, indipendenti dai tipi di datocoinvolti: 

#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 è dataalcuna indicazione circa il tipo di a e b:essa utilizza l'operatore ? :, che puòessere  applicato ad ogni tipo di dato[32]. Ilprogrammatore è perciò libero di utilizzarla inqualunque 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 certorilievo (sommare le pere alle mele, come si dice...). Insecondo luogo, le macro prestano il fianco ai cosiddettiside­effect, o effetti collaterali. Il Cimplementa un particolare operatore, detto di autoincremento[33], che accresce di una unità ilvalore della variabile a cui è anteposto: se applicatoa uno dei parametri coinvolti nella macro, esso vieneapplicato più volte al parametro, producendo risultatiindesiderati: 

int var1, var2;    int minimo;    ....    minimo = min(++var1, var2);

La macrosotituzione effettuata dal preprocessore trasformal'ultima riga dell'esempio nella seguente: 

minimo = ((++var1 < var2) ? ++var1 : var2);

E' facile vedere che esso si limita a sostituire allamacro min la definizione data con la#define, sostituendo altresì i parametria e b con i simboli utilizzati al loroposto nella riga di codice, cioè ++var1 evar2. In tal modo var1 èincrementata due volte se dopo il primo incremento essarisulta ancora minore di var2, una sola volta nelcaso opposto. Se min() fosse una funzione ilproblema non potrebbe verificarsi (una chiamata a funzionenon è una semplice sostituzione di stringhe, maun'operazione tradotta in linguaggio macchina dalcompilatore seguendo precise regole); tuttavia una funzionenon accetterebbe indifferentemente argomenti di vario tipo, eoccorrerebbe definire funzioni diverse per effettuareconfronti, di volta in volta, tra integer, tra floatingpoint, e così via. Un altro esempio di effettocollaterale riguarda più da vicino il comportamentodel 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 nonserve più, la si può annullare con la direttiva#undefine. Ogni riferimento alla definizioneannullata, successivamente inserito nel programma, dàluogo ad una segnalazione di errore da parte delcompilatore. 

Da notare che PROG_NAME è passata aprintf() senza porla tra virgolette, in quanto essesono già parte della stringa di sostituzione, come sipuò vedere nell'esempio. Se si fossero utilizzatele virgolette, printf() avrebbe scrittoPROG_NAME e non PROVA: il preprocessore,infatti, ignora tutto quanto è racchiuso travirgolette o apici. In altre parole, esso non ficca il nasonelle costanti stringa e in quelle di tipocarattere. 

Vale la pena di citare anche la direttiva#ifdef...#else...#endif, che consente di includere oescludere dalla compilazione un parte di codice, a secondache sia, o meno, definita una determinata costantemanifesta: 

#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 risultanoparticolarmente utili per scrivere codice portabile: le partidi sorgente differenti in dipendenza dal compilatore, dalsistema o dalla macchina possono essere escluse o inclusenella compilazione con la semplice definizione di unacostante manifesta in testa al sorgente. 

 



Ti potrebbe interessare anche

commenta la notizia