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.