Le macro semplici!:
La definizione di una macro semplice ha la forma:
#define identificatore elenco-di-sostituzione
L’elenco di sostituzione è una qualsiasi sequenza di token del processore.
L’elenco di sostituzione di una macro può contenere identificatori,keyword,costanti
numeriche,costanti carattere,stringhe letterali,operatori e caratteri di interpunzione.
Quando incontriamo la definizione di una macro il preprocessore prende nota del fatto
che l’identificatore rappresenta l’elenco di sostituzione. Ogni volta che nella parte
successiva del file viene incontrato l’identificatore,questo viene sostituito
con l’elenco di sostituzione.
CONSIGLIO:
/*******************************************************
Non mettere simboli aggiuntivi all’interno della definizione di una macro,quindi
diventerebbero parte della lista di sostituzione. Mettere il simbolo * nella
definizione di una macro è un errore piuttosto comune:
#define N = 100 /*** SBAGLIATO***/
…
int a[N]; /* diventa int a [= 100];*/
In questo esempio abbiamo erroneamente definito N come una coppia di token (= e 100).
Un altro errore frequente è quello di terminare la definizione di una macro con un
punto e virgola.
#define N = 100 /*** SBAGLIATO***/
…
int a[N]; /* diventa int a [100;];*/
Con questa definizione N corrisponde ai token 100 e ;.
Il compilatore si accorgerà della maggior parte degli errori causati da simboli
aggiunti nelle definizioni delle macro. Sfortunatamente il compilatore segnalerà
come un errore ogni utilizzo della macro invece di segnalare il vero colpevole
(la definizione della macro) che è stato rimosso dal preprocessore.
**************************************************************/
Le macro semplici sono utilizzate principalmente per definire le cosi dette
“costanti manifeste”. Utilizzando le macro possiamo assegnare nomi ai valori numerici
a caratteri e stringhe.
#define STR_LEN 80
#define TRUE 1
#define FALSE 0
#define PI 3,14159
#define CR ‘r’
#define EOS ”
#define MEM_ERR “Errore della memoria”
Utilizzare #define per assegnare dei nomi alla costanti ha diversi
vantaggi significativi
* Rende i programmi più facili da leggere: Il nome della macro (se scelto bene) aiuta
il lettore a comprendere il significato della costante. L’alternativa è un programma
pieno di “numeri magici” che possono disorientare facilmente il lettore.
* Rende i programmi più facili da modificare: Modificando la sola definizione della
macro possiamo cambiare il valore di una costante in tutto il programma.
Le costanti codificate “in modo fisso” sono molto più difficili da modificare,
soprattutto se qualche volta compaiono in una forma leggermente modificare (per esempio
un programma con un vettore di lunghezza 100 può avere un ciclo che va da 0 a 99.
Se cercassimo semplicemente le occorrenze di 100 all’interno del programma,
non troveremo il 99)
* Aiuta a evitare incosistenze ed errori tipografici: Se una costante numerica come
3.14159 comprare diverse volte, ci sono buone probabilità che venga scritta per errore
come 3.14116 o 3.14159.
Sebbene le macro semplici vengono utilizzate molto spesso per definire il nome delle
costanti,possono essere utilizzate anche per altri scopi.
* Effettuare dei piccoli combiamenti alla sintassi del C: In effetti possiamo alterare
la sintassi del C definendo delle macro da utilizzare come nomi alternativi per i
simboli del C. Per esempio i programmatori che preferiscono i token begin ed end
del Pascal alle parentesi { e } del C possono denire le seguenti macro:
#define BEGIN {
#define END }
* Rinominare i tipi: ad esempio creare un tipo booleano rinominando il tipo int:
#define BOOL int
anche se poco usato.
* Controllare la compilazione condizionale: Le macro giocano un ruolo importante
nella compilazione condizionale. Per esempio la presenza della seguente riga in un
programma può indicare che questo debba essere compilato nella “modalità di debug”
ovvero con istruzioni aggiuntive per produrre dell’output utile per il
debugging:
#define DEBUG
Per inciso possiamo dire che è possibile avere delle macro con l’elenco di
sostituzione vuoto cosi come vediamo nell’esempio appena presentato.
Di solito i programmatori C hanno l’abitudine di utilizzare solo lettere maiuscole
per i nomi delle macro che vengono utilizzate come costanti. Tuttavia non vi è
consenso su come scrivere le macro utilizzate per altri scopi. Dato che queste
(specialmente quelle parametriche) sono fonte di bache,ad alcuni programmatori piace
attirare l’attenzione su di esse utilizzando solo lettere maiuscole per i loro nomi.
Ma se si è abituati con le minuscole va benissimo non c’è una regola.
Le macro parametriche:
La definizione di una macro parametrica ha la seguente forma:
#define identificatore(x1,x2,…,xn) elenco-di-sostituzione
/************NOTA*****************
Non devono esserci spazi tra il nome della macro e la parentesi tonda sinistra. Se
viene lasciato dello spazio,il preprocessore penserà che si sta definendo una macro
semplice e tratterà (x1,x2,…,xn) come parte dell’elenco di sostituzione.
/*****************************************
Quando il prepocessore incontra una definizione di una macro parametrica,memorizza
la sua definizione per gli utilizzi successivi. Ogni volta che un invocazione della
macro della forma identificatore(y1,y2…,yn) comprare nel programma
(dove y1,y2,…,yn sono sequenze di token), il preprocessore la rimpiazza con
l’elenco di sostituzione rimpiazzando x1 con y1, x2 con y2 e cosi via..
Per esempio,supponete di aver definito le seguenti macro:
#define MAX(xy) ((x)>(y)?(x):(y))
#define IS_EVEN(n) ((n)%2==0)
Anche se il numero di parentesi sembra alta c’è una ragione che vedremo in futuro..
Supponete ora di invocare le due macro in questo modo:
i 0 MAX(j+k, m-n)
if (IS_EVEN8i)) i++;
il preprocessore rimpiazzerà queste righe con:
i = ((j+k)>(m-n)?(j+k):(m-n))
if (((i)%2==0)) i++;
Come mostra questo esempio,spesso le macro parametriche fungono da semplici funzioni.
MAX si comporta come una funzione che calcola il maggiore tra due numeri.
IS_EVEN si comporta come una funzione che restituisce 1 se il suo argomento è un
numero pari,altrimenti restituisce 0.
Ecco una macro più complessa che si comprta come una funzione:
#define TOUPPER(c) (‘a’<=(c)&&(c)<=’z'?(c)-a’a'+’A':(c))
Questa macro controlla se il carattere c è compreso tra ‘a’ e ‘z’. Se è cosi produce
la versione maiuscola di c sottraendo ‘a’ e sommando ‘A’. Se non è cosi,la macro non
modifica c (in ctype.h contine una funzione di nome toupper che è più portabile.)
Una macro parametrica può avere un elenco di parametri vuoto. Ecco un esempio:
#define getchar() getc(stdin)
L’elenco vuoto di parametri non è necessario ma da in modo che getchar somigli a una
funzione (si è la stessa getchar che appartiene a <stdio.h> vedremo in futuro che di
solito la getchar è implementata come una macro oltre che come una funzione).
Utilizzare una macro parametri in luogo di una vere funzione presenta diversi vantaggi:
VANTAGGI:
* il programma può essere leggermente più veloce: Solitamente una chiamata a funzione
è causa di overhead durante l’esecuzione del programma(le informazioni sul contesto
devono essere salvate,gli argomenti devono essere copiati e cosi via). L’invocazione
di una macro,d’altro canto,non rchiede alcun overhead.
* Le macro sono “generiche”: I parametri delle macro a differenza dei parametri delle
funzioni, non possiedono un tipo particolare . Come risultato una macro può accettare
argomenti di qualsiasi tipo particolare. Come risultato una macro può accettare
argomenti di qualsiasi tipo, a patto che il programma risultante dopo il
preprocessamento sia valido. Per esempio possiamo utlizzare la macro MAX per trovare
il maggiore tra due valori di tipo int,long,float ecc..
SVANTAGGI:
*Il codice compilato spesso è di maggiori dimensioni: Ogni invocazione di macro provoca
l’inserimento dell’elenco di sostituzione e quindi l’incremento delle dimensioni del
sorgente del programma (e quindi l’incremento del codice compilato).
Più spesso viene utilizzata una macro e più l’effetto è pronunciato. Il problema
si aggrava quando le invocazioni delle macro vengono annidate.Considerate quello che
succede quando utilizziamo MAX presentata sopra per trovare il maggiore tra tre numeri:
n= MAX(i,MAX(j,k));
Ecco l’istruzione dopo il preprocessamento
n = ((i)>(((j)>(k)?(j):(k)))?(i):(((j)>(k)?(j):(k)));
* Non viene controllato il tipo degli argomenti: Quando una funzione viene invocata,
il compialtore controlla ogni argomento per vedere se è del tipo appropiato. Nel caso
non lo fosse, o l’argomento viene convertito nel tipo appropiato oppure il compilatore
produce un messaggio di errore. Gli argomenti delle macro non vengono controllati
dal preprocessore e quindi non vengono convertiti.
* non è possibile avere un puntatore a una macro: Come già visto in una guida fatta
da me il C permette puntatori a funzione, un concetto che è piuttosto utile in alcune
situazioni di programmazione. Le macro vengono rimosse durante il preprocessamento e
quindi non c’è un concetto di “puntatore a una macro”. Il risultato è che le macro non
possono essere utilizzate in queste situazioni.
* una macro può calcolare i suoi argomenti più volte: Una funzione calcola i suoi
argomenti solamente una volta. Una macro può calcolare i suoi argomenti due o più
volte. Calcolrare un argomento più di una volta può provocare un comportamento
inaspettato se l’argomento possiede dei side effect. Considerate cosa succede se uno
degli argomenti di MAX posside un side effect:
n = MAX(i++,j)
Ecco come si presenta la stessa riga dopo il preprocessamento:
n = ((i++)>(j)?(i++):(j));
Se i è maggiore di j allora i verrà (erroneamente) incrementata due volte e a n
verrà assegnato un valore non atteso.
/********ATTENZIONE********************************************
Gli errori quando un argomento di una macro viene calcolato più di una volta possono
essere difficili da trovare perchè l’invocazione della macro sembra uguale a una
chiamata a funzione. A peggiorare le cose c’è il fatto che una macro può funzionare
correttamente la maggior parte delle volte,creando problemi solo con certi argomenti
che possiedono dei side effect. Per prevenire inconvenienti è meglio evitare side
effect negli argomenti.
/***************************************************************
ART N 4:
[C] Macro: Macro parametriche pt 4:
Le macro parametriche non sono apprezzabili solo per la semplice simulazione delle
funzioni. In particolare, vengono utilizzate spesso come pattern di segmenti di codice
che noi stessi possiamo trovare ripetitivi. Supponete di annoiavi nello scrivere:
printf(“%dn”,i);
ogni volta che abbiamo bisogno di stampare un intero. Possiamo definire la seguente
macro che rende più facile la visualizzazione degli interi:
#define PRINT_INT(n) printf(“%dn”,n)
Una volta che PRINT_INT è stata definita, il preprocessore convertirà la riga
PRINT_INT(i/j);
in
printf(“%dn”, i/j);
