Come funziona il Preprocessore

Il preprocessore è la prima fase della compilazione. Viene eseguito prima del compilatore C/C++ e trasforma il testo del sorgente:

1. Rimuove commenti

Tutti i commenti // ... e /* ... */ vengono rimossi.

2. Include file

#include copia letteralmente il contenuto del file header.

3. Espande macro

#define fa sostituzione testuale nel codice.

4. Compilazione condizionale

#if, #ifdef includono/escludono blocchi di codice.

BashVisualizza l'output del preprocessore
gcc -E file.c -o file_preprocessato.c
# Poi: cat file_preprocessato.c per vedere il risultato

1. Include Guards e #pragma once

CProtezione da inclusioni multiple
// METODO 1: Include guard tradizionale (portabile)
#ifndef MIOHEADER_H
#define MIOHEADER_H

// Tutto il contenuto del header qui...
struct Punto { int x, y; };
void muovi(struct Punto *p, int dx, int dy);

#endif // MIOHEADER_H

// ------------------------------------------------

// METODO 2: #pragma once (supportato da GCC, Clang, MSVC)
#pragma once

// Contenuto del header...
// Pro: più semplice, ma non standard C/C++
// Contro: non funziona se il file viene copiato fisicamente in due posti

2. Macro con Parametri

CMacro function-like e le loro insidie
#include <stdio.h>

// Macro semplici
#define PI     3.14159265358979
#define MAX_N  1000

// Macro con parametri — ATTENZIONE alle parentesi!
#define MAX(a, b)     ((a) > (b) ? (a) : (b))
#define MIN(a, b)     ((a) < (b) ? (a) : (b))
#define ABS(x)        ((x) >= 0 ? (x) : -(x))
#define QUADRATO(x)   ((x) * (x))

// PROBLEMA con macro: double evaluation
#define MAX_WRONG(a,b)  a > b ? a : b  // Senza parentesi: 2+3 > 4 → sbagliato

// Confronta con una funzione inline:
static inline int max_inline(int a, int b) { return a > b ? a : b; }

// Macro multi-riga con do-while (pattern idiomatico)
#define SWAP(T, a, b) \
    do {              \
        T _tmp = (a); \
        (a) = (b);    \
        (b) = _tmp;   \
    } while(0)

// Macro per logging con file e riga
#define LOG_INFO(fmt, ...)  \
    fprintf(stderr, "[INFO] %s:%d: " fmt "\n", __FILE__, __LINE__, ##__VA_ARGS__)

#define LOG_ERROR(fmt, ...) \
    fprintf(stderr, "[ERROR] %s:%d: " fmt "\n", __FILE__, __LINE__, ##__VA_ARGS__)

// Macro per assert custom
#define MY_ASSERT(cond) \
    do { \
        if (!(cond)) { \
            fprintf(stderr, "Assertion failed: %s (%s:%d)\n", #cond, __FILE__, __LINE__); \
            __builtin_trap(); \
        } \
    } while(0)

// Stringify: converte un'espressione in stringa
#define STRINGIFY(x) #x
#define TO_STRING(x) STRINGIFY(x)

// Token pasting: incolla due token
#define CONCAT(a, b) a##b

int main() {
    printf("MAX(3,5) = %d\n", MAX(3, 5));
    printf("ABS(-7) = %d\n", ABS(-7));
    printf("QUADRATO(4) = %d\n", QUADRATO(4));

    // INSIDIA: QUADRATO(1+2) → ((1+2)*(1+2)) = 9 OK con parentesi
    // Senza parentesi: 1+2*1+2 = 5 SBAGLIATO

    // SWAP
    int x=10, y=20;
    SWAP(int, x, y);
    printf("Dopo SWAP: x=%d y=%d\n", x, y);

    double a=3.14, b=2.71;
    SWAP(double, a, b);
    printf("Dopo SWAP double: a=%.2f b=%.2f\n", a, b);

    // Log
    LOG_INFO("Valore di x = %d", x);
    LOG_ERROR("Simulazione errore %d", 404);

    // Stringify
    printf("PI si chiama: %s = %.5f\n", STRINGIFY(PI), PI);

    // Token pasting
    int valore_1 = 100, valore_2 = 200;
    printf("valore_1 = %d\n", CONCAT(valore_, 1));  // Diventa valore_1

    // Assert
    MY_ASSERT(x == 20);  // OK
    // MY_ASSERT(x == 99); // Farebbe crash con messaggio

    return 0;
}

3. Compilazione Condizionale

C#ifdef, #if, #elif per build condizionali
#include <stdio.h>

// ===== Platform detection =====
#if defined(_WIN32) || defined(_WIN64)
    #define PIATTAFORMA "Windows"
    #define PATH_SEP '\\'
#elif defined(__APPLE__)
    #define PIATTAFORMA "macOS"
    #define PATH_SEP '/'
#elif defined(__linux__)
    #define PIATTAFORMA "Linux"
    #define PATH_SEP '/'
#else
    #define PIATTAFORMA "Sconosciuto"
    #define PATH_SEP '/'
#endif

// ===== Build type (definisci con -DDEBUG o -DRELEASE) =====
#ifdef DEBUG
    #define DBG_PRINT(fmt, ...) fprintf(stderr, "[DBG] " fmt "\n", ##__VA_ARGS__)
    #define PERFORMANCE_CHECK 0
#else
    #define DBG_PRINT(fmt, ...) /* nop — rimosso in release */
    #define PERFORMANCE_CHECK 1
#endif

// ===== Versione API =====
#define API_MAJOR 2
#define API_MINOR 5
#define API_PATCH 1
#define API_VERSION_INT (API_MAJOR*10000 + API_MINOR*100 + API_PATCH)
#define API_VERSION_STR TO_STRING(API_MAJOR) "." TO_STRING(API_MINOR) "." TO_STRING(API_PATCH)

// Usa TO_STRING dalla sezione precedente
#ifndef TO_STRING
#define STRINGIFY(x) #x
#define TO_STRING(x) STRINGIFY(x)
#endif

// ===== Feature flags =====
// Definisci ENABLE_LOGGING=1 per abilitare i log
#if ENABLE_LOGGING
    #include <time.h>
    #define LOG(msg) printf("[%ld] %s\n", time(NULL), msg)
#else
    #define LOG(msg) /* nop */
#endif

// ===== Controllo versione del compilatore =====
#if __STDC_VERSION__ >= 201112L
    #define C_STD "C11 o superiore"
#elif __STDC_VERSION__ >= 199901L
    #define C_STD "C99"
#else
    #define C_STD "Pre-C99"
#endif

int main() {
    printf("Piattaforma:  %s\n", PIATTAFORMA);
    printf("Standard C:   %s\n", C_STD);
    printf("Versione API: %s (int=%d)\n", API_VERSION_STR, API_VERSION_INT);

    DBG_PRINT("Questo appare solo in modalità DEBUG");

    // Compilazione condizionale in runtime equivalente (SBAGLIATA):
    // if (DEBUG) printf("debug"); ← Il codice è sempre compilato!
    // Con #ifdef il codice non compilato NON esiste nell'eseguibile

    return 0;
}

4. Macro Variadic e Avanzate

C__VA_ARGS__, X-Macros, macro ricorsive
#include <stdio.h>
#include <stdarg.h>

// Macro variadic con __VA_ARGS__
#define DEBUG_PRINT(fmt, ...) \
    printf("[%s:%d] " fmt "\n", __func__, __LINE__, ##__VA_ARGS__)

// Macro per generare codice ripetitivo (X-Macro pattern)
// Definiamo i dati UNA VOLTA, usiamo le macro per generare enum, stringhe, etc.
#define COLORI_LISTA \
    X(ROSSO,   "Rosso",   255, 0,   0  ) \
    X(VERDE,   "Verde",   0,   255, 0  ) \
    X(BLU,     "Blu",     0,   0,   255) \
    X(GIALLO,  "Giallo",  255, 255, 0  ) \
    X(BIANCO,  "Bianco",  255, 255, 255)

// Genera l'enum
typedef enum {
    #define X(nome, ...) COLORE_##nome,
    COLORI_LISTA
    #undef X
    NUM_COLORI
} Colore;

// Genera array di nomi
const char *nomi_colori[] = {
    #define X(nome, stringa, ...) stringa,
    COLORI_LISTA
    #undef X
};

// Genera array di valori RGB
typedef struct { int r, g, b; } RGB;
RGB valori_rgb[] = {
    #define X(nome, stringa, r, g, b) {r, g, b},
    COLORI_LISTA
    #undef X
};

// Macro per creare struct con getter automatici (metaprogrammazione)
#define DECLARE_PROPERTY(T, name) \
    T name;                        \
    T get_##name() { return name; } \
    void set_##name(T val) { name = val; }

// Contare elementi in array a compile time
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof(arr[0]))

int main() {
    DEBUG_PRINT("Numero di colori: %d", NUM_COLORI);

    for (int i=0; i<NUM_COLORI; i++) {
        printf("Colore %d: %-8s rgb(%d,%d,%d)\n",
               i, nomi_colori[i],
               valori_rgb[i].r, valori_rgb[i].g, valori_rgb[i].b);
    }

    // Array size
    int arr[] = {1,2,3,4,5,6,7,8,9,10};
    printf("Array size: %zu\n", ARRAY_SIZE(arr));

    return 0;
}

5. Macro Predefinite

CMacro standard del preprocessore
#include <stdio.h>

int main() {
    printf("File:            %s\n", __FILE__);
    printf("Linea:           %d\n", __LINE__);
    printf("Funzione:        %s\n", __func__);    // C99
    printf("Data build:      %s\n", __DATE__);
    printf("Ora build:       %s\n", __TIME__);
    printf("Standard C:      %ld\n", __STDC_VERSION__);  // 201710L = C17

    // GCC specifiche
    printf("GCC versione:    %s\n", __VERSION__);
    printf("x86-64?          %s\n",
    #ifdef __x86_64__
        "Sì"
    #else
        "No"
    #endif
    );

    // Utili per debugging
    #define TRACE() printf("TRACE: %s:%s:%d\n", __FILE__, __func__, __LINE__)
    TRACE();
    TRACE();

    return 0;
}
🏋️ Esercizi
1
Sistema di logging con macro
FacileMacro

Crea un sistema di logging con 4 livelli (DEBUG, INFO, WARN, ERROR) usando macro. Ogni messaggio deve mostrare il livello, la funzione, il file e la riga. Aggiungi una macro LOG_LEVEL per filtrare i messaggi sotto un certo livello (compile-time filtering).

C
/* log.h */
#ifndef LOG_H
#define LOG_H

#include <stdio.h>

#define LOG_LEVEL_DEBUG 0
#define LOG_LEVEL_INFO  1
#define LOG_LEVEL_WARN  2
#define LOG_LEVEL_ERROR 3

// Imposta il livello minimo con -DMIN_LOG_LEVEL=1 (default: DEBUG)
#ifndef MIN_LOG_LEVEL
#define MIN_LOG_LEVEL LOG_LEVEL_DEBUG
#endif

#define _LOG(level, label, color, fmt, ...) \
    do { \
        if ((level) >= MIN_LOG_LEVEL) \
            fprintf(stderr, color "[%-5s] %s:%d " fmt "\033[0m\n", \
                    label, __func__, __LINE__, ##__VA_ARGS__); \
    } while(0)

#define LOG_DEBUG(fmt, ...) _LOG(LOG_LEVEL_DEBUG, "DEBUG", "\033[36m",  fmt, ##__VA_ARGS__)
#define LOG_INFO(fmt,  ...) _LOG(LOG_LEVEL_INFO,  "INFO",  "\033[32m",  fmt, ##__VA_ARGS__)
#define LOG_WARN(fmt,  ...) _LOG(LOG_LEVEL_WARN,  "WARN",  "\033[33m",  fmt, ##__VA_ARGS__)
#define LOG_ERROR(fmt, ...) _LOG(LOG_LEVEL_ERROR, "ERROR", "\033[31m",  fmt, ##__VA_ARGS__)

#endif

/* main.c */
/* #include "log.h" */

void processa(int valore) {
    LOG_DEBUG("Elaboro valore: %d", valore);
    if (valore < 0)  { LOG_WARN("Valore negativo: %d", valore); return; }
    if (valore == 0) { LOG_ERROR("Valore zero non permesso!"); return; }
    LOG_INFO("Elaborazione completata, risultato: %d", valore * 2);
}

int main() {
    processa(5);
    processa(-3);
    processa(0);
    return 0;
}
2
X-Macro per stati di una macchina
MedioX-Macros

Usa il pattern X-Macro per definire gli stati di una macchina a stati finiti (es: connessione di rete: DISCONNESSO, CONNETTENDO, CONNESSO, ERRORE). Genera automaticamente: l'enum degli stati, l'array dei nomi, e le funzioni di transizione valide.

C
#include <stdio.h>

#define STATI_FSM \
    X(DISCONNESSO,  "Disconnesso",  "Attesa connessione") \
    X(CONNETTENDO,  "Connettendo",  "Tentativo in corso") \
    X(CONNESSO,     "Connesso",     "Sessione attiva")    \
    X(ERRORE,       "Errore",       "Connessione fallita")

// Genera enum
typedef enum {
    #define X(id, ...) STATO_##id,
    STATI_FSM
    #undef X
    NUM_STATI
} Stato;

// Genera array di nomi
const char *nomi_stato[] = {
    #define X(id, nome, ...) nome,
    STATI_FSM
    #undef X
};

const char *desc_stato[] = {
    #define X(id, nome, desc) desc,
    STATI_FSM
    #undef X
};

// Tabella delle transizioni valide [da][a] = 1 se valido
int transizioni[NUM_STATI][NUM_STATI] = {
/*              DISCONN  CONNET  CONN  ERRORE */
/* DISCONN */  {0,       1,      0,    0},
/* CONNET  */  {1,       0,      1,    1},
/* CONN    */  {1,       0,      0,    1},
/* ERRORE  */  {1,       0,      0,    0},
};

int transita(Stato *corrente, Stato prossimo) {
    if (transizioni[*corrente][prossimo]) {
        printf("Transizione: %s → %s\n",
               nomi_stato[*corrente], nomi_stato[prossimo]);
        *corrente = prossimo;
        return 1;
    }
    printf("Transizione INVALIDA: %s → %s\n",
           nomi_stato[*corrente], nomi_stato[prossimo]);
    return 0;
}

int main() {
    Stato s = STATO_DISCONNESSO;
    printf("Stato iniziale: %s (%s)\n\n", nomi_stato[s], desc_stato[s]);

    transita(&s, STATO_CONNETTENDO);    // OK
    transita(&s, STATO_CONNESSO);       // OK
    transita(&s, STATO_DISCONNESSO);    // OK dal connesso
    transita(&s, STATO_CONNESSO);       // INVALIDO: disconnesso→connesso diretto
    transita(&s, STATO_CONNETTENDO);    // OK
    transita(&s, STATO_ERRORE);         // OK

    printf("\nStato finale: %s\n", nomi_stato[s]);
    return 0;
}