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.
Object-like Macros (Costanti)
Section titled “Object-like Macros (Costanti)”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.
Function-like Macros
Section titled “Function-like Macros”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!
I Pericoli: Parentesi e Side-effects
Section titled “I Pericoli: Parentesi e Side-effects”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é:
- Crea un blocco di codice sicuro in cui dichiarare variabili.
- Viene eseguito esattamente una volta.
- 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.