Il Preprocessore
Macro avanzate, include guards, compilazione condizionale, debugging con il preprocessore.
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.
gcc -E file.c -o file_preprocessato.c
# Poi: cat file_preprocessato.c per vedere il risultato
1. Include Guards e #pragma once
// 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
#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
#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
#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
#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;
}
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).
/* 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;
}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.
#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;
}