Skip to content

Macro e Sostituzione Testuale

La direttiva #define serve a creare una Macro. Il pre-processore cercherà nel codice tutte le occorrenze del nome della macro e le sostituirà con il testo (Replacement List) che le hai assegnato, un po’ come un Trova e Sostituisci automatico del tuo editor di testo.

La forma più semplice di macro viene usata per definire parametri fisici o “magic numbers” (costanti magiche) nel codice:

#define PI_GRECO 3.14159
#define CAPACITA_MAX 100
int array[CAPACITA_MAX]; // Diventerà letteralmente: int array[100];

La validità della macro dura dal punto in cui viene definita fino alla fine del file, oppure fino a quando non la elimini esplicitamente usando la direttiva #undef CAPACITA_MAX.

Le macro possono anche accettare parametri ed essere invocate in modo simile a una funzione:

#define QUADRATO(x) ((x) * (x))
int a = QUADRATO(5); // Diventa: int a = ((5) * (5));

Il vantaggio? Sono rapidissime perché non c’è il costo (overhead) a livello CPU per la chiamata a funzione, e sono generiche, cioè funzionano per qualsiasi tipo di dato (float, int, double) senza doverlo specificare!

Le Function-like Macros sono note per essere un campo minato. Poiché la sostituzione è puramente “testuale” e ignora le regole di precedenza degli operatori matematici, guarda cosa succede senza parentesi:

#define DOPPIO(x) x * 2
int y = DOPPIO(3 + 1);
// Diventerà: int y = 3 + 1 * 2; -> Risultato: 5 (anziché 8!)

Regola d’oro: Quando scrivi una macro con argomenti, racchiudi sempre ogni occorrenza del parametro e l’intera espressione finale tra parentesi!

#define DOPPIO_SICURO(x) ((x) * 2)

Un altro difetto noto (che porta spesso a Undefined Behavior) è l’effetto collaterale (Side Effect) quando passi parametri che si auto-modificano, ad esempio una macro per il calcolo del valore assoluto:

#define BAD_ABS(x) ((x) >= 0 ? (x) : -(x))
int i = -5;
int ris = BAD_ABS(i++);
// Diventa: int ris = ((i++) >= 0 ? (i++) : -(i++));

La variabile i viene valutata e incrementata più volte! Le macro “non sicure” non dovrebbero mai essere usate con argomenti che hanno effetti collaterali (++, --, chiamate di funzioni).

Macro Multi-Istruzione: L’idioma do-while(0)

Section titled “Macro Multi-Istruzione: L’idioma do-while(0)”

Un problema classico si presenta quando una macro contiene più istruzioni separate da punto e virgola. Guarda questo esempio:

#define LOG_ERROR() printf("Errore!\n"); exit(1)

Se provi ad usare questa macro all’interno di un costrutto if senza parentesi graffe, causerai un grave bug logico (e sintattico):

if (errore)
LOG_ERROR();

Il preprocessore lo espanderà così:

if (errore)
printf("Errore!\n");
exit(1); // Questo viene eseguito SEMPRE, indipendentemente dall'if!

Per risolvere il problema in modo sicuro ed elegante, lo standard industriale e l’idioma C preferito è raggruppare le istruzioni in un blocco do { ... } while(0):

#define LOG_ERROR() do { printf("Errore!\n"); exit(1); } while(0)

Questo trucco funziona perché:

  1. Crea un blocco di codice sicuro in cui dichiarare variabili.
  2. Viene eseguito esattamente una volta.
  3. Costringe l’utente ad aggiungere il punto e virgola finale per chiudere il while(0);, rendendo l’uso della macro identico a quello di una normale funzione C.

💻 Esercizio Pratico (Correggere una Macro)

Section titled “💻 Esercizio Pratico (Correggere una Macro)”

Il seguente codice usa una macro difettosa MOLTIPLICA che non rispetta la regola delle parentesi. Risolvi il bug.