Direttive per il preprocessore: la direttiva #define, #if e #ifdef
La direttiva #define
La direttiva serve a definire una MACRO, ovvero un simbolo.
La sintassi con cui utilizzare la direttiva è la seguente:
#define nome_macro valore_macro
convenzionalmente i nomi delle macro vengono scritti con delle lettere MAIUSCOLE. Il preprocessore legge la definizione della MACRO e, ogni volta che ne incontra il nome all'interno del file sorgente SOSTITUISCE al simbolo il corrispondente valore, SENZA verificare la correttezza sintattica dell'espressione risultante.
// esempio di utilizzo di una macro
#include <stdio.h>
#define ULTIMO 30
int main()
{int
i = 0;
for(i = 0; i< ULTIMO;i++)
printf("il numero e\' %d ed il massimo valore e\' %d",i,ULTIMO);
return 0; }
// fine esempio
Dopo l'azione del preprocessore, la funzione main() avrà questo aspetto :
int main()
{int i = 0;
for(i = 0; i< 30;i++)
printf("il numero e\' %d ed il massimo valore e\' %d",i,30);
return 0; }
Ovvero
il preprocessore ha sostituito il simbolo ULTIMO con il corrispondente valore.
Il valore della MACRO può essere formato da un numero arbitrario di caratteri; se risultasse necessario andare a capo, basta inserire un carattere \ al termine della riga.
Ad esempio, nel caso fosse necessario stampare molte volte il medesimo messaggio, si può definire una macro di questo tipo:
#include <stdio.h>
#define AVVISO printf("\nil programma, fino a qui, funziona in modo corretto");
int main()
{char c;
c = getchar();
... // istruzioni
AVVISO
... // altre istruzioni
AVVISO
... // istruzioni
}
grazie al preprocessore, ovvero effettuando la sostituzione, questo codice si trasforma in :
int main()
{char c;
c = getchar();
... // istruzioni
printf("\nil programma, fino a qui, funziona in modo corretto");
... // altre istruzioni
printf("\nil programma, fino a qui, funziona in modo corretto");
... // istruzioni
}
Grazie alla direttiva #define si possono definire anche delle macro che svolgono operazioni un po' più complesse. Ad esempio
#include <stdio.h>
#define STAMPA(a) printf("\nil valore della variabile e\' pari a %d",a);
int main()
{ int a=6, b=9;
STAMPA(a)
STAMPA(b)
STAMPA(a+b)
return 0;}
produce il seguente output:
il valore della variabile e' pari a 6
il valore della variabile e' pari a 9
il valore della variabile e' pari a 15
ATTENZIONE: la sostituzione può nascondere dei problemi.
#include <stdio.h>
#define MOLTIPLICA(a,b) a*b
int main()
{int a=5,b=3,c;
c = MOLTIPLICA(a,b); // tutto OK ! diventa c = a * b;
c = MOLTIPLICA( a + b, b ); // ?? diventa c = a + b * b;
return 0; }
Per ovviare al problema basta inserire delle parentesi ...
#define MOLTIPLICA(a,b) (a) * (b)
Direttive per la compilazione condizionale: #if ed #ifdef
Così come, mediante il costrutto if() era possibile controllare il flusso del programma, grazie alle direttiva #if e #ifdef è possibile controllare il flusso della compilazione, ovvero stabilire quali porzioni del codice verranno compilate e quali no. La sintassi con cui utilizzare le due direttive è la seguente:
#if espressione_costante
... codice
#else
... codice
#endif
|
#ifdef nome_macro
... codice
#endif |
Se l'espressione costante è vera, allora viene compilata la prima porzione del codice; altrimenti viene compilata la seconda |
Se la macro è stata definita, allora viene compilata la porzione di codice compresa tra #ifdef ed #endif; altrimenti tale porzione di codice viene saltata |
NOTA: le due macro vanno SEMPRE concluse da #endif
// esempio : programma per convertire euro in sterline o dollari
#include <stdio.h>
#define MONETA STERLINA
#define UK
int main()
{double coeff = 1;
int numero ;
#if MONETA==STERLINA
coeff = 0.6943;
#else
coeff = 1.2509;
#endif
printf("\n inserisci la valuta in euro");
numero = getchar();
printf("\n %d euro corrispondono a %f", numero , numero/coeff);
#ifdef UK
printf(" sterline");
#else
printf(" dollari");
#endif
return 0; }
// fine esempio
Provate a riprodurre questo esempio usando l'opzione -E del compilatore. A seconda del valore della macro MONETA vedrete che l'output del preprocessore cambia, ovvero cambia il codice che di volta in volta viene compilato.
Viceversa, usando il costrutto if() TUTTO il codice viene sempre compilato, ma solo una porzione viene eseguita.
IMPORTANTE: non è necessario definire una macro all'interno del codice !!! lo possiamo tranquillamente fare anche dalla riga di comando.
gcc -Wall -o test -DNOMEMACRO[=VALOREMACRO] programma.c
-DNOMEMACRO[=VALOREMACRO]
NOMEMACRO rappresenta il nome della macro
VALOREMACRO è opzionale e rappresentail valore della macro
Prendiamo come esempio il programma di prima. Modificando il codice in questo modo:
#include <stdio.h>
int main()
{double coeff = 1;
int numero ;
#if MONETA==STERLINA
coeff = 0.6943;
#else
coeff = 1.2509;
#endif
printf("\n inserisci la valuta in euro");
numero = getchar();
printf("\n %d euro corrispondono a %f", numero , numero/coeff);
#ifdef UK
printf(" sterline");
#else
printf(" dollari");
#endif
return 0; }
// fine esempio
e compilando con questo comando
gcc -Wall -o test -DMONETA=STERLINA -DUK programma.c
otteniamo il medesimo risultato ottenuto prima.
Nota:
chi usa il compilatore Dev-Cpp, per poter usare la stessa metodologia, deve scegliere l'opzione "Opzioni di compilazione" sotto il menù strumenti ed inserire, assieme al -Wall queste nuove istruzioni per il compilatore

Esercizio:
Per mettere in pratica tutto questo, costruiamo un programma in cui la medesima procedura viene implementata sia in modo ricorsivo che in modo iterativo. La scelta di quale procedura utilizzare avverà in fase di compilazione e NON in fase di esecuzione, ovvero nel codice eseguibile NON CI SARA' TRACCIA dell'implementazione della funzione che non è stata selezionata.
La funzione da implementare è quella che ci serve a visualizzare il valore dei bit che compongono la rappresentazione binaria di una variabile di tipo intero.
Il metodo da utilizzare è quello visto qualche giorno fa: data una variabile di tipo int, per visualizzare il valore del bit della sua rappresentazione che si trova nella posizione corrispondente alla potenza di 2 di ordine n è sufficiente dividere il numero stesso per la medesima potenza di 2 e poi applicare l'operatore modulo %2.
10011000010100101011001010100001 mi interessa il bit corrispondente a 2 elevato a 5
------> passo 1 10011000010100101011001010100001 / 2^5
------> passo 2 00000100110000101001010110010101
------> passo 3 00000100110000101001010110010101 % 2 = 1 ! ecco il valore del bit
Se abbiamo a che fare con interi, dovremmo dividere il numero per potenze di 2 che variano da 0 a 31.
// Implementazione iterativa
#include <stdio.h>
#include <math.h>
void show_bits_i(int);
void show_bits_i(int numero)
{int i=0;
for(i = 31; i >=0; i--)
printf("%d",((int) (numero/pow(2,i))) %2 );
} // non serve l'istruzione return, la funzione è di tipo void
// Implementazione ricorsiva (1)
// questa implementazione riprende il metodo presentato alcune lezioni fa per la visualizzazione dei bit di
//
una variabile
void show_bits_r(int);
void show_bits_r(int numero)
{
if(numero>0) // sarebbe stato equivalente scrivere if(numero)
{printf("%d", numero %2 );
i++;
show_bits_r(numero/2);}
}
In questo modo, però, stampo solo alcuni bit della sequenza di 32, non tutti !
// Implementazione ricorsiva (2)
void show_bits_r(int);
void show_bits_r(int numero)
{static int i=31;
if(i>0) // sarebbe stato equivalente scrivere if(i)
{printf("%d", (int) (numero/pow(2,i)) %2 );
i++;
show_bits_r(numero);}
}
Quale differenza notiamo rispetto all'implementazione ricorsiva presentata ieri ?
/* codice finale - inizio . supponiamo di aver inserito le due implementazioni descritte sopra all'interno del file personale myfun.h */
#include "myfun.h"
#include <stdlib.h>
int main()
{char car1,car2;
int inizio,i;
printf("\n inserisci un carattere");
car1 = mygetchar()
printf("\n inserisci un altro carattere");
car2 = mygetchar()
srand(car1*car2); // inizializzo il generatore di numeri casuali
for(i=0;i<20;i++)
{
#ifdef RICORSIVO
show_bits_r( rand() ); // implementazione ricorsiva
#else
show_bits_i( rand() ); // implementazione iterativa
#endif
// codice finale - fine
Se l'utente definisce la macro RICORSIVO allora viene compilata SOLO LA PRIMA PARTE mentre se l'utente non la definisce viene automaticamente compilata la seconda.