Barninga Z
a- a+

Le costanti manifeste

LE COSTANTI

Le costanti manifeste 

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 non­spazio; 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 side­effect, 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. 

 



Ti potrebbe interessare anche

commenta la notizia

C'è 1 commento
Marcello
Ti è piaciuto l'articolo?