COMPILAZIONE
GCC — Compilazione Base
# Compilazione + linking in un colpo
gcc -std=c99 -Wall -Wextra -pedantic main.c -o main

# Solo preprocessing (-E), compilazione (-S), assemblaggio (-c)
gcc -E main.c -o main.i       # output preprocessato
gcc -S main.c -o main.s       # output assembly
gcc -c main.c -o main.o       # output oggetto

# Più file sorgente
gcc -std=c99 main.c utils.c -o app

# Con librerie esterne
gcc main.c -lm -lpthread -o app   # -l linka la libreria
Flag Comuni
FlagDescrizione
-std=c99Standard C99
-WallAbilita la maggior parte dei warning
-WextraWarning extra oltre -Wall
-WerrorTratta i warning come errori
-pedanticAderenza stretta allo standard
-gInformazioni di debug (per GDB)
-O0 / -O1 / -O2 / -O3 / -OsLivelli di ottimizzazione
-D NOME=valDefinisce macro da riga di comando
-I dirAggiunge directory per header include
-L dirAggiunge directory per librerie
-fsanitize=addressAddressSanitizer (buffer overflow, use-after-free)
-fsanitize=undefinedUBSan (comportamento indefinito)
Makefile Minimo
CC      = gcc
CFLAGS  = -std=c99 -Wall -Wextra -pedantic -g
LDFLAGS = -lm
SRC     = main.c utils.c
OBJ     = $(SRC:.c=.o)
TARGET  = app

$(TARGET): $(OBJ)
	$(CC) $(OBJ) $(LDFLAGS) -o $@

%.o: %.c
	$(CC) $(CFLAGS) -c $< -o $@

clean:
	rm -f $(OBJ) $(TARGET)

.PHONY: clean
STRUTTURA DI UN PROGRAMMA C
#include <stdio.h>      // header libreria standard
#include "myheader.h"   // header locale

// Dichiarazione forward (prototipo)
int somma(int a, int b);

// Variabile globale
int g_counter = 0;

// Punto di ingresso
int main(int argc, char *argv[]) {
    printf("Somma: %d\n", somma(3, 4));
    return 0;
}

// Definizione funzione
int somma(int a, int b) {
    return a + b;
}
Header Guard (Pattern)
/* myheader.h */
#ifndef MYHEADER_H
#define MYHEADER_H

int somma(int a, int b);
void print_info(const char *msg);

#endif /* MYHEADER_H */
Unità di Traduzione

Ogni file .c + i suoi #include forma un'unità di traduzione compilata indipendentemente. Il linker unisce i file .o nel binario finale.

TIPI DI DATO
Tipi Interi
TipoDimensione tipicaRange
char1 byte-128 .. 127 (o 0..255 se unsigned)
short2 byte-32768 .. 32767
int4 byte-2³¹ .. 2³¹-1
long4/8 bytealmeno 32 bit
long long8 byte-2⁶³ .. 2⁶³-1 (C99)
_Bool1 byte0 o 1 (C99)

Ogni tipo intero può essere signed (default per int/short/long) o unsigned.

Tipi Virgola Mobile
TipoDimensionePrecisione
float4 byte~7 cifre decimali
double8 byte~15 cifre decimali
long double10/12/16 byte≥ double (impl. defined)
void

void — tipo incompleto: nessun valore. Usato per funzioni senza ritorno, puntatori generici (void *), e cast espliciti.

sizeof
sizeof(int)          // dimensione del tipo in byte
sizeof(arr)          // dimensione totale dell'array (se non decaduto)
sizeof(arr)/sizeof(arr[0])   // numero elementi array
sizeof(char)         // sempre 1 per definizione
Qualificatori di Tipo
QualificatoreSignificato
constValore non modificabile dopo l'inizializzazione
volatileValore può cambiare esternamente (HW, signal handler)
restrictIl puntatore è l'unico accesso a quell'area di memoria (C99)
LETTERALI & COSTANTI
Letterali Numerici
42           // int decimale
042          // int ottale (= 34)
0x2A         // int esadecimale (= 42)
42L          // long
42LL         // long long (C99)
42U          // unsigned int
42ULL        // unsigned long long
3.14         // double
3.14f        // float
3.14L        // long double
1.5e3        // 1500.0 (notazione scientifica)
0x1.8p1     // hex float: 3.0 (C99)
Letterali Carattere & Stringa
'A'          // char (valore int = 65)
'\n'         // newline
'\t'         // tab
'\0'         // null terminator
'\\'         // backslash
'\x41'       // hex escape (= 'A')
'\077'       // octal escape (= '?')

"hello"      // string literal (const char[6])
"hello" " world"   // concatenazione automatica
Costanti con #define e const
#define PI       3.14159265358979
#define MAX_BUF  1024

const int MAX_SIZE = 100;     // non utilizzabile come dimensione array statico (pre-VLA)
enum { LIMIT = 256 };         // costante intera "vera" (compile-time)
OPERATORI
Precedenza (dall'alto = più alta)
Prec.OperatoriAssociatività
1() [] -> . ++ -- (postfisso)Sinistra
2++ -- + - ! ~ * & (type) sizeof (prefisso/unario)Destra
3* / %Sinistra
4+ -Sinistra
5<< >>Sinistra
6< <= > >=Sinistra
7== !=Sinistra
8& (bitwise AND)Sinistra
9^ (bitwise XOR)Sinistra
10| (bitwise OR)Sinistra
11&&Sinistra
12||Sinistra
13?: (ternario)Destra
14= += -= *= /= %= <<= >>= &= ^= |=Destra
15, (virgola)Sinistra
Operatori Aritmetici & Relazionali
a + b    a - b    a * b    a / b    a % b   // aritmetici
a == b   a != b   a < b    a > b             // relazionali
a <= b   a >= b                               // relazionali
!a       a && b   a || b                      // logici
a ? b : c                                     // ternario
Operatori Bitwise
a & b    // AND bit a bit
a | b    // OR bit a bit
a ^ b    // XOR bit a bit
~a       // NOT bit a bit (complemento a 1)
a << n   // shift sinistro
a >> n   // shift destro (aritmetico per signed, logico per unsigned)
Operatori di Assegnamento Composto
a += b   a -= b   a *= b   a /= b   a %= b
a &= b   a |= b   a ^= b   a <<= b  a >>= b
Incremento / Decremento
int x = 5;
int a = x++;   // a = 5, poi x = 6  (post-incremento)
int b = ++x;   // x = 7, poi b = 7  (pre-incremento)
Operatore Virgola
int x = (1, 2, 3);   // x = 3 (valuta tutto, restituisce l'ultimo)
for (int i=0, j=10; i < j; i++, j--)  // uso tipico nel for
CAST & CONVERSIONI IMPLICITE
Promozioni Intere (Integer Promotion)

Tipi più piccoli di int (char, short, _Bool, bitfield) vengono promossi a int (o unsigned int) prima di qualsiasi operazione aritmetica.

Conversioni Aritmetiche Usuali
// Gerarchia: long double > double > float > unsigned long long > long long > ...
// L'operando di tipo inferiore viene convertito al tipo superiore

int    a = 5;
double b = 2.5;
double c = a + b;     // a promosso a double → 7.5

int      x = -1;
unsigned y = 1;
// x + y → x convertito a unsigned! -1 diventa UINT_MAX → BUG COMUNE
⚠ Confronti signed/unsigned: -1 < 1U è falso perché -1 diventa UINT_MAX. Usare -Wsign-compare.
Cast Esplicito
double d = 3.99;
int n = (int)d;              // troncamento → 3

void *p = malloc(100);
int *ip = (int *)p;          // cast da void* (in C non necessario ma usuale)

int a = 7, b = 2;
double r = (double)a / b;    // 3.5 (senza cast: 3)
SELEZIONE (if / switch)
if / else if / else
if (x > 0) {
    printf("positivo\n");
} else if (x == 0) {
    printf("zero\n");
} else {
    printf("negativo\n");
}
switch
switch (scelta) {
    case 1:
        printf("uno\n");
        break;
    case 2:
    case 3:
        printf("due o tre\n");
        break;
    default:
        printf("altro\n");
        break;
}
I case devono essere espressioni intere costanti. Senza break si ha fall-through.
ITERAZIONE (for / while / do-while)
// for — C99 permette dichiarazione nell'init
for (int i = 0; i < n; i++) {
    printf("%d ", i);
}

// while
int i = 0;
while (i < n) {
    printf("%d ", i++);
}

// do-while — esegue almeno una volta
int c;
do {
    c = getchar();
} while (c != '\n' && c != EOF);
SALTI (break / continue / goto / return)
break;         // esce dal loop o switch più interno
continue;      // salta alla prossima iterazione del loop
return expr;   // esce dalla funzione restituendo expr

// goto — usare con parsimonia (cleanup pattern)
int *buf = malloc(1024);
if (!buf) goto cleanup;
FILE *f = fopen("file.txt", "r");
if (!f) goto cleanup_buf;
// ... lavoro ...
fclose(f);
cleanup_buf:
    free(buf);
cleanup:
    return -1;
Il goto è idiomatico in C per la gestione errori con pulizia risorse (pattern usato nel kernel Linux).
FUNZIONI — DICHIARAZIONE & DEFINIZIONE
Prototipo vs Definizione
// Prototipo (dichiarazione) — tipicamente nell'header
int massimo(int a, int b);

// Definizione — nel file .c
int massimo(int a, int b) {
    return (a > b) ? a : b;
}

// Funzione void senza parametri: usare (void) esplicitamente in C
void stampa_banner(void) {
    puts("=== BANNER ===");
}
int f() senza void in C significa "accetta qualsiasi numero di argomenti", non "zero argomenti"!
Passaggio per Valore & per Riferimento (via puntatore)
// Per valore — la funzione lavora su una copia
void raddoppia(int x) { x *= 2; }  // nessun effetto sul chiamante

// Per riferimento — si passa il puntatore
void raddoppia(int *x) { *x *= 2; } // modifica la variabile esterna

int val = 5;
raddoppia(&val);  // val = 10
Passaggio di Array a Funzione
// L'array decade a puntatore — si perde la dimensione
void stampa(int arr[], size_t n) {
    for (size_t i = 0; i < n; i++)
        printf("%d ", arr[i]);
}

// Equivalente a:
void stampa(int *arr, size_t n);

// Array multidimensionale — colonne fisse
void mat_print(int rows, int cols, int m[rows][cols]);  // C99 VLA param
static nelle Funzioni
// Funzione static: visibilità limitata all'unità di traduzione
static int helper(int x) { return x * 2; }

// Variabile static: persiste tra le chiamate
int contatore(void) {
    static int count = 0;   // inizializzata solo una volta
    return ++count;
}
FUNZIONI VARIADIC
#include <stdarg.h>

int somma_n(int count, ...) {
    va_list args;
    va_start(args, count);      // inizializza la lista

    int totale = 0;
    for (int i = 0; i < count; i++)
        totale += va_arg(args, int);  // estrae il prossimo argomento

    va_end(args);               // pulizia
    return totale;
}

// Uso:
somma_n(3, 10, 20, 30);  // → 60
MacroDescrizione
va_listTipo che contiene le info sugli argomenti variabili
va_start(ap, last)Inizializza ap; last è l'ultimo parametro fisso
va_arg(ap, type)Estrae il prossimo argomento di tipo type
va_end(ap)Pulizia della lista
va_copy(dest, src)Copia la lista (C99)
RICORSIONE
// Fattoriale — ricorsione classica
unsigned long factorial(unsigned int n) {
    if (n <= 1) return 1;
    return n * factorial(n - 1);
}

// Tail-recursive (il compilatore può ottimizzare con -O2)
unsigned long fact_tail(unsigned int n, unsigned long acc) {
    if (n <= 1) return acc;
    return fact_tail(n - 1, n * acc);
}

// Fibonacci — ricorsione doppia (inefficiente senza memoizzazione)
int fib(int n) {
    if (n <= 1) return n;
    return fib(n-1) + fib(n-2);
}
⚠ Ogni chiamata ricorsiva consuma spazio sullo stack. Per profondità elevate, preferire la versione iterativa.
PUNTATORI
Dichiarazione & Operatori Base
int  x  = 42;
int *p  = &x;       // p punta a x
int  y  = *p;       // dereferenziazione: y = 42
*p = 100;            // x diventa 100

int **pp = &p;       // puntatore a puntatore
**pp = 200;          // x diventa 200

int *q = NULL;       // puntatore nullo — dereferenziare = UB
const e Puntatori
const int *p;         // puntatore a int costante (non si può modificare *p)
int *const p;         // puntatore costante a int (non si può modificare p)
const int *const p;  // entrambi costanti
Regola: const si applica a ciò che sta alla sua sinistra. Se non c'è nulla a sinistra, si applica a destra.
void* — Puntatore Generico
void *generic = &x;
// Non si può dereferenziare direttamente — serve cast
int val = *(int *)generic;

// void* è il tipo di ritorno di malloc/calloc/realloc
// In C, la conversione void* ↔ T* è implicita (a differenza del C++)
ARRAY
Dichiarazione & Inizializzazione
int a[5];                          // non inizializzato (valori indefiniti)
int b[5] = {1, 2, 3};              // {1, 2, 3, 0, 0}
int c[5] = {0};                     // tutti zero
int d[]  = {10, 20, 30};             // dimensione dedotta: 3

// Designated initializers (C99)
int e[10] = {[0] = 1, [5] = 50, [9] = 99};
Array Multidimensionali
int mat[3][4];                        // 3 righe, 4 colonne
int mat[3][4] = {
    {1, 2, 3, 4},
    {5, 6, 7, 8},
    {9, 10, 11, 12}
};

// Accesso: mat[riga][col]
// In memoria: layout row-major (righe contigue)
Relazione Array/Puntatore
int arr[5] = {10, 20, 30, 40, 50};
int *p = arr;          // arr decade a &arr[0]

arr[2]  == *(arr + 2)  // equivalenti
p[2]    == *(p + 2)    // equivalenti
&arr[2] == arr + 2     // equivalenti
sizeof(arr) dà la dimensione totale dell'array, ma sizeof(p) dà la dimensione del puntatore (4/8 byte).
ARITMETICA DEI PUNTATORI
int arr[5] = {10, 20, 30, 40, 50};
int *p = arr;

p + 3        // punta a arr[3] — avanza di 3 * sizeof(int) byte
p - 1        // punta all'elemento precedente
p++          // avanza al prossimo elemento
p2 - p1      // differenza in numero di elementi (ptrdiff_t)

// Iterazione con puntatore
for (int *p = arr; p < arr + 5; p++)
    printf("%d ", *p);

// Confronto puntatori: solo tra puntatori allo stesso array/oggetto
if (p1 < p2) { /* p1 precede p2 */ }
PUNTATORI A FUNZIONE
// Dichiarazione
int (*fp)(int, int);         // puntatore a funzione (int,int)→int

// Assegnazione
fp = massimo;                 // oppure fp = &massimo

// Chiamata
int r = fp(3, 7);             // oppure (*fp)(3, 7)

// Typedef per leggibilità
typedef int (*BinOp)(int, int);
BinOp op = massimo;
op(3, 7);

// Array di puntatori a funzione
BinOp ops[] = { somma, sottrai, moltiplica };
ops[1](10, 3);   // chiama sottrai(10, 3)

// Callback — passare funzione come argomento
void applica(int *arr, size_t n, int (*f)(int)) {
    for (size_t i = 0; i < n; i++)
        arr[i] = f(arr[i]);
}
qsort — Esempio con Callback
#include <stdlib.h>

int cmp_int(const void *a, const void *b) {
    const int ia = *(const int *)a;
    const int ib = *(const int *)b;
    return (ia > ib) - (ia < ib);  // sicuro: nessun overflow
    // return ia - ib;  ← ⚠ overflow se ia e ib hanno segno opposto e valori grandi
}

int arr[] = {5, 2, 8, 1, 9};
qsort(arr, 5, sizeof(int), cmp_int);
STRINGHE C
// Stringa C = array di char terminato da '\0'
char s1[] = "hello";          // char[6]: {'h','e','l','l','o','\0'}
char *s2   = "hello";          // puntatore a string literal (read-only!)
char s3[20] = "hello";        // buffer più grande, resto = '\0'

// ATTENZIONE: s2 punta a memoria read-only
s1[0] = 'H';     // OK
// s2[0] = 'H';   // UNDEFINED BEHAVIOR!

// Lunghezza
size_t len = strlen(s1);    // 5 (non conta '\0')
size_t siz = sizeof(s1);    // 6 (conta '\0')
FUNZIONI string.h
FunzioneDescrizione
strlen(s)Lunghezza (senza '\0')
strcpy(dst, src)Copia stringa (⚠ nessun bounds check)
strncpy(dst, src, n)Copia al max n char (attenzione: potrebbe non terminare con '\0')
strcat(dst, src)Concatena src a dst
strncat(dst, src, n)Concatena al max n char
strcmp(a, b)Confronta: <0, 0, >0
strncmp(a, b, n)Confronta primi n char
strchr(s, c)Trova prima occorrenza di c (ritorna puntatore o NULL)
strrchr(s, c)Trova ultima occorrenza di c
strstr(hay, needle)Trova prima occorrenza di sottostringa
strtok(s, delim)Tokenizzazione (modifica la stringa, non thread-safe)
memcpy(dst, src, n)Copia n byte (no overlap)
memmove(dst, src, n)Copia n byte (gestisce overlap)
memset(s, c, n)Imposta n byte al valore c
memcmp(a, b, n)Confronta n byte
Pattern Sicuro per Stringhe
// snprintf è più sicuro di sprintf (bounds-checked)
char buf[64];
snprintf(buf, sizeof(buf), "Nome: %s, Età: %d", nome, eta);

// Duplicazione stringa (POSIX, non standard C99)
char *copia = strdup(originale);  // malloc + strcpy — ricordarsi di free()
STRUCT
// Definizione
struct Punto {
    double x;
    double y;
};

// Dichiarazione e inizializzazione
struct Punto p1 = {1.0, 2.5};
struct Punto p2 = {.y = 3.0, .x = 1.0};   // designated init (C99)

// Accesso ai campi
p1.x = 5.0;

// Puntatore a struct → operatore freccia
struct Punto *pp = &p1;
pp->x = 10.0;       // equivale a (*pp).x

// Copia di struct (copia membro per membro)
struct Punto p3 = p1;

// Struct con array flessibile (C99)
struct Buffer {
    size_t len;
    char   data[];   // flexible array member — deve essere l'ultimo campo
};
struct Buffer *b = malloc(sizeof(struct Buffer) + 100);
b->len = 100;
Struct Annidate & Padding
struct Rettangolo {
    struct Punto origine;
    double larghezza, altezza;
};

// Padding: il compilatore inserisce byte di allineamento
struct Bad  { char a; int b; char c; };  // sizeof = 12 (con padding)
struct Good { int b; char a; char c; };  // sizeof = 8  (meno padding)
Ordinare i campi dal più grande al più piccolo riduce il padding.
UNION
// Tutti i campi condividono la stessa area di memoria
union Dato {
    int    i;
    float  f;
    char   s[4];
};

union Dato d;
d.i = 42;
printf("%d\n", d.i);    // 42
d.f = 3.14f;
// d.i ora contiene la rappresentazione binaria di 3.14f

sizeof(union Dato)  // = max(sizeof(int), sizeof(float), 4) = 4

// Tagged union pattern
struct Variant {
    enum { VAR_INT, VAR_FLOAT, VAR_STR } tag;
    union {
        int   i;
        float f;
        char  s[32];
    } val;
};
ENUM
enum Colore { ROSSO, VERDE, BLU };          // 0, 1, 2
enum Stato  { OFF = 0, ON = 1, STANDBY = 5 }; // valori espliciti

enum Colore c = VERDE;

// Enum come flag bitmask
enum Perm {
    PERM_READ    = 1 << 0,   // 1
    PERM_WRITE   = 1 << 1,   // 2
    PERM_EXECUTE = 1 << 2,   // 4
};
unsigned perms = PERM_READ | PERM_WRITE;
if (perms & PERM_READ) { /* ha permesso lettura */ }
In C gli enum sono int. Non c'è type-safety — si possono assegnare interi arbitrari.
TYPEDEF
// Alias per tipi semplici
typedef unsigned long ulong;
typedef unsigned char byte;

// Typedef per struct (idiomatico in C)
typedef struct {
    double x, y;
} Punto;

Punto p = {1.0, 2.0};   // non serve "struct Punto"

// Typedef per puntatori a funzione
typedef int (*Comparator)(const void *, const void *);

// Typedef per strutture opache (forward declaration)
typedef struct Nodo Nodo;   // per strutture auto-referenziali
struct Nodo {
    int   data;
    Nodo *next;
};
BIT FIELDS
struct Flags {
    unsigned int attivo   : 1;   // 1 bit
    unsigned int priorita : 3;   // 3 bit (0-7)
    unsigned int tipo     : 4;   // 4 bit (0-15)
};

struct Flags f = {.attivo = 1, .priorita = 5, .tipo = 3};
f.priorita = 7;

// Il layout in memoria è implementation-defined
// Non si può prendere l'indirizzo di un bit field: &f.attivo è illegale
CLASSI DI MEMORIA (Storage Class)
SpecifierScopeLifetimeNote
autoBloccoBloccoDefault per variabili locali (mai usato esplicitamente)
registerBloccoBloccoSuggerimento al compilatore (ignorato con -O). No & operator
static (locale)BloccoProgrammaPersiste tra le chiamate, inizializzata a zero se non esplicita
static (globale/fn)FileProgrammaLinkage interno: visibile solo nell'unità di traduzione
externGlobaleProgrammaDichiara una variabile/funzione definita altrove
(nessuno, globale)GlobaleProgrammaLinkage esterno (visibile da altre unità di traduzione)
extern — Esempio Multi-file
/* globals.c */
int g_count = 0;          // definizione

/* globals.h */
extern int g_count;       // dichiarazione

/* main.c */
#include "globals.h"
g_count++;                  // usa la variabile definita in globals.c
ALLOCAZIONE DINAMICA (stdlib.h)
FunzioneDescrizione
malloc(size)Alloca size byte non inizializzati. Ritorna NULL se fallisce
calloc(n, size)Alloca n*size byte inizializzati a zero
realloc(ptr, size)Ridimensiona il blocco; può spostare. realloc(NULL,n) = malloc(n)
free(ptr)Libera la memoria. free(NULL) è un no-op
// Allocare un array dinamico di 100 int
int *arr = malloc(100 * sizeof(*arr));
if (arr == NULL) {
    perror("malloc");
    exit(EXIT_FAILURE);
}

// Usare l'array
for (int i = 0; i < 100; i++)
    arr[i] = i * i;

// Ridimensionare
int *tmp = realloc(arr, 200 * sizeof(*arr));
if (tmp == NULL) {
    free(arr);    // realloc fallito — arr è ancora valido
    exit(EXIT_FAILURE);
}
arr = tmp;

// Liberare
free(arr);
arr = NULL;   // buona pratica: evita dangling pointer
Allocazione Matrice Dinamica
// Matrice rows x cols
int **mat = malloc(rows * sizeof(*mat));
for (int i = 0; i < rows; i++)
    mat[i] = malloc(cols * sizeof(**mat));

// Deallocazione — ordine inverso
for (int i = 0; i < rows; i++)
    free(mat[i]);
free(mat);

// Alternativa: singola allocazione contigua (cache-friendly)
int *flat = malloc(rows * cols * sizeof(*flat));
// accesso: flat[i * cols + j]
⚠ Errori comuni: memory leak (dimenticare free), double free, use-after-free, buffer overflow. Usare Valgrind o ASan per diagnosticare.
PREPROCESSORE — DIRETTIVE
DirettivaDescrizione
#include <h>Include header di sistema
#include "h"Include header locale (cerca prima nella dir corrente)
#define NAME valDefinisce macro oggetto
#define M(a,b) ...Definisce macro funzione
#undef NAMERimuove la definizione di una macro
#if exprCompilazione condizionale
#ifdef NAMESe NAME è definita
#ifndef NAMESe NAME non è definita
#elif exprElse if
#elseElse
#endifFine blocco condizionale
#pragmaDirettiva specifica del compilatore
#error "msg"Genera errore di compilazione
#line n "file"Cambia numero riga e nome file
Compilazione Condizionale — Pattern
#ifdef DEBUG
    printf("Debug: x = %d\n", x);
#endif

#if defined(__linux__)
    // codice Linux
#elif defined(_WIN32)
    // codice Windows
#elif defined(__APPLE__)
    // codice macOS
#else
    #error "Piattaforma non supportata"
#endif
MACRO AVANZATE
// Macro funzione
#define MAX(a, b)       ((a) > (b) ? (a) : (b))
#define SQUARE(x)       ((x) * (x))

// ⚠ Le parentesi sono essenziali per evitare problemi di precedenza
// ⚠ Argomenti con side-effect: SQUARE(i++) → ((i++) * (i++)) → UB

// Stringification: # trasforma in stringa
#define STR(x)          #x
STR(hello)     // → "hello"

// Token pasting: ## concatena token
#define CONCAT(a, b)    a##b
CONCAT(var, 1)  // → var1

// Macro multi-linea con do { } while(0)
// Versione con tipo esplicito (standard C99)
#define SWAP_INT(a, b) do { \
    int _tmp = (a); \
    (a) = (b); \
    (b) = _tmp; \
} while(0)

// Versione generica con typeof (estensione GCC/Clang, non standard C99)
#define SWAP(a, b) do { \
    typeof(a) _tmp = (a); \
    (a) = (b); \
    (b) = _tmp; \
} while(0)

// Variadic macro (C99)
#define LOG(fmt, ...)   fprintf(stderr, fmt, __VA_ARGS__)
#define LOG2(fmt, ...)  fprintf(stderr, fmt, ##__VA_ARGS__)  // ## elimina la , se nessun arg (GCC ext)

// Macro per dimensione array
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
MACRO PREDEFINITE
MacroDescrizione
__FILE__Nome del file sorgente corrente
__LINE__Numero di riga corrente
__func__Nome della funzione corrente (C99)
__DATE__Data di compilazione "Mmm dd yyyy"
__TIME__Ora di compilazione "hh:mm:ss"
__STDC__1 se il compilatore è conforme allo standard
__STDC_VERSION__199901L per C99, 201112L per C11, ecc.
printf("Errore in %s:%d (%s)\n", __FILE__, __LINE__, __func__);

#if __STDC_VERSION__ >= 199901L
    // codice C99+
#endif
I/O STANDARD (stdio.h)
Output
printf("Hello %s, age %d\n", nome, eta);
putchar('A');          // stampa un carattere
puts("hello");          // stampa stringa + newline
fprintf(stderr, "Errore: %s\n", msg);   // output su stream
sprintf(buf, "x=%d", x);              // scrive in stringa (⚠ overflow)
snprintf(buf, sizeof(buf), "x=%d", x); // sicuro: limita a n byte
Input
int n;
scanf("%d", &n);           // legge intero da stdin

char buf[100];
fgets(buf, sizeof(buf), stdin);   // legge una riga (incluso \n)

int c = getchar();        // legge un carattere (ritorna int o EOF)

sscanf(str, "%d %f", &n, &f);  // parsing da stringa
⚠ Non usare mai gets() — buffer overflow garantito. Usare fgets().
Stream Standard
StreamTipoDescrizione
stdinFILE*Standard input (fd 0)
stdoutFILE*Standard output (fd 1) — buffered
stderrFILE*Standard error (fd 2) — unbuffered
I/O SU FILE
Apertura & Chiusura
ModalitàDescrizione
"r"Lettura (il file deve esistere)
"w"Scrittura (crea o tronca)
"a"Append (crea se non esiste)
"r+"Lettura + scrittura (il file deve esistere)
"w+"Lettura + scrittura (crea o tronca)
"a+"Lettura + append
"rb"Modalità binaria (importante su Windows)
FILE *f = fopen("data.txt", "r");
if (f == NULL) {
    perror("fopen");
    return 1;
}

// Lettura riga per riga
char line[256];
while (fgets(line, sizeof(line), f)) {
    printf("%s", line);
}

// Lettura formattata
int x; float y;
fscanf(f, "%d %f", &x, &y);

// Scrittura
FILE *out = fopen("output.txt", "w");
fprintf(out, "valore: %d\n", x);
fputs("linea di testo\n", out);

// Lettura/scrittura binaria
int arr[10];
fwrite(arr, sizeof(int), 10, out);   // scrive 10 int
fread(arr, sizeof(int), 10, f);     // legge 10 int

// Posizionamento
fseek(f, 0, SEEK_SET);     // inizio file
fseek(f, 0, SEEK_END);     // fine file
long pos = ftell(f);        // posizione corrente
rewind(f);                  // torna all'inizio

fclose(f);
fclose(out);
Funzioni Utili
FunzioneDescrizione
feof(f)True se si è raggiunta la fine del file
ferror(f)True se c'è stato un errore sullo stream
fflush(f)Svuota il buffer di output
remove("file")Cancella un file
rename("old","new")Rinomina un file
tmpfile()Crea file temporaneo (auto-delete)
SPECIFICATORI DI FORMATO (printf / scanf)
printf
Spec.TipoDescrizione
%d / %iintIntero decimale con segno
%uunsignedIntero decimale senza segno
%ounsignedOttale
%x / %XunsignedEsadecimale min/maiuscolo
%fdoubleVirgola mobile (default 6 decimali)
%e / %EdoubleNotazione scientifica
%g / %GdoubleSceglie %f o %e (il più corto)
%cintCarattere
%schar*Stringa
%pvoid*Puntatore (indirizzo)
%nint*Scrive il numero di char scritti finora
%%Stampa un % letterale
%zusize_tsize_t (C99)
%lldlong longlong long (C99)
Modificatori di Formato
printf("%10d", x);      // allineamento destra, larghezza 10
printf("%-10d", x);     // allineamento sinistra
printf("%010d", x);     // padding con zeri
printf("%+d", x);       // mostra sempre il segno
printf("%.2f", f);      // 2 cifre decimali
printf("%.*f", n, f);   // precisione variabile (passata come argomento)
printf("%.5s", str);    // stampa al max 5 char della stringa
printf("%#x", x);       // prefisso 0x per esadecimale
printf("%#o", x);       // prefisso 0 per ottale
stdlib.h — FUNZIONI UTILITÀ
FunzioneDescrizione
atoi(s) / atol(s) / atof(s)Conversione stringa → int/long/double (no error check)
strtol(s, &end, base)String → long con error check e base variabile
strtod(s, &end)String → double con error check
strtoll(s, &end, base)String → long long (C99)
rand()Pseudo-random [0, RAND_MAX]
srand(seed)Seed per rand()
abs(n) / labs(n) / llabs(n)Valore assoluto int/long/long long
div(a,b) / ldiv / lldivQuoziente e resto in una struct
exit(status)Termina il programma (chiama atexit handlers)
_Exit(status)Termina senza atexit/flush (C99)
atexit(fn)Registra funzione da chiamare all'uscita
abort()Termina con SIGABRT
system(cmd)Esegue comando shell
getenv("VAR")Legge variabile d'ambiente
qsort(arr, n, size, cmp)Ordinamento generico
bsearch(key, arr, n, size, cmp)Ricerca binaria su array ordinato
strtol — Pattern di Conversione Sicura
char *input = "42abc";
char *end;
long val = strtol(input, &end, 10);

if (end == input)
    printf("Nessun numero trovato\n");
else if (*end != '\0')
    printf("Parziale: %ld (residuo: '%s')\n", val, end);
else
    printf("Numero: %ld\n", val);
math.h — FUNZIONI MATEMATICHE

Compilare con -lm per linkare la libreria matematica.

FunzioneDescrizione
sqrt(x) / cbrt(x)Radice quadrata / cubica
pow(x, y)x elevato a y
exp(x) / log(x) / log10(x) / log2(x)Esponenziale e logaritmi
fabs(x)Valore assoluto (double)
fabsf(x) / fabsl(x)float / long double (C99)
ceil(x) / floor(x)Arrotondamento su / giù
round(x) / trunc(x)Arrotondamento / troncamento (C99)
fmod(x, y)Resto virgola mobile
remainder(x, y)Resto IEEE (C99)
sin(x) / cos(x) / tan(x)Trigonometria (radianti)
asin(x) / acos(x) / atan(x)Inverse trigonometriche
atan2(y, x)Arcotangente a 2 argomenti
sinh / cosh / tanhIperboliche
hypot(x, y)√(x² + y²) (C99)
INFINITY / NANCostanti speciali (C99)
isnan(x) / isinf(x) / isfinite(x)Test valori speciali (C99)
ctype.h — CLASSIFICAZIONE CARATTERI
FunzioneTrue se
isalpha(c)Lettera (a-z, A-Z)
isdigit(c)Cifra (0-9)
isalnum(c)Lettera o cifra
isspace(c)Whitespace (spazio, tab, newline, ecc.)
isupper(c) / islower(c)Maiuscola / minuscola
ispunct(c)Punteggiatura
isprint(c)Stampabile (incluso spazio)
iscntrl(c)Carattere di controllo
isxdigit(c)Cifra esadecimale
toupper(c) / tolower(c)Conversione maiuscola/minuscola
L'argomento deve essere unsigned char o EOF. Passare char con valori negativi è UB.
stdint.h & inttypes.h (C99)
Tipi a Larghezza Fissa
TipoBitDescrizione
int8_t / uint8_t8Esattamente 8 bit
int16_t / uint16_t16Esattamente 16 bit
int32_t / uint32_t32Esattamente 32 bit
int64_t / uint64_t64Esattamente 64 bit
intptr_t / uintptr_tptrCapace di contenere un puntatore
size_tRisultato di sizeof (unsigned, stddef.h)
ptrdiff_tDifferenza tra puntatori (signed, stddef.h)
intmax_t / uintmax_tmaxIl tipo intero più grande supportato
Costanti & Macro di Formato
#include <stdint.h>
#include <inttypes.h>

int64_t big = INT64_MAX;           // 9223372036854775807
uint32_t u = UINT32_MAX;          // 4294967295

printf("val = %" PRId64 "\n", big);   // PRId64 = formato portabile per int64_t
printf("hex = %" PRIx32 "\n", u);    // PRIx32 per uint32_t in hex

sscanf(str, "%" SCNd64, &big);     // SCN* per scanf
stdbool.h & ALTRI HEADER C99
stdbool.h
#include <stdbool.h>

bool flag = true;
if (flag) { /* ... */ }
// bool, true, false sono macro per _Bool, 1, 0
complex.h (C99)
#include <complex.h>

double complex z = 1.0 + 2.0 * I;
double re = creal(z);    // parte reale
double im = cimag(z);    // parte immaginaria
double m  = cabs(z);     // modulo
tgmath.h (C99) — Type-Generic Math
#include <tgmath.h>
// Usa automaticamente la versione corretta (float/double/long double)
float f = sqrt(2.0f);       // chiama sqrtf
double d = sqrt(2.0);       // chiama sqrt
Riepilogo Header C99 Importanti
HeaderContenuto
<stdbool.h>bool, true, false
<stdint.h>Tipi a larghezza fissa (int32_t, ecc.)
<inttypes.h>Macro formato per tipi stdint (PRI*, SCN*)
<complex.h>Numeri complessi
<tgmath.h>Funzioni matematiche type-generic
<fenv.h>Controllo ambiente floating-point
limits.h & float.h — LIMITI DEI TIPI
limits.h — Tipi Interi
CostanteValore tipicoDescrizione
CHAR_BIT8Bit per byte
CHAR_MIN / CHAR_MAX-128 / 127Range di char (signedness impl-defined)
SCHAR_MIN / SCHAR_MAX-128 / 127signed char
UCHAR_MAX255unsigned char
SHRT_MIN / SHRT_MAX-32768 / 32767short
USHRT_MAX65535unsigned short
INT_MIN / INT_MAX-2³¹ / 2³¹-1int
UINT_MAX2³²-1unsigned int
LONG_MIN / LONG_MAXalmeno ±2³¹long (4 o 8 byte)
ULONG_MAXalmeno 2³²-1unsigned long
LLONG_MIN / LLONG_MAX-2⁶³ / 2⁶³-1long long (C99)
ULLONG_MAX2⁶⁴-1unsigned long long (C99)
float.h — Virgola Mobile
CostanteValore tipico (double)Descrizione
FLT_RADIX2Base dell'esponente
FLT_DIG / DBL_DIG / LDBL_DIG6 / 15 / 18Cifre decimali di precisione
FLT_EPSILON / DBL_EPSILON1.19e-7 / 2.22e-16Più piccolo ε t.c. 1.0 + ε ≠ 1.0
FLT_MIN / DBL_MIN1.17e-38 / 2.22e-308Più piccolo positivo normalizzato
FLT_MAX / DBL_MAX3.4e38 / 1.8e308Valore massimo
FLT_MIN_EXP / FLT_MAX_EXP-125 / 128Esponente min/max (base 2)
FLT_MANT_DIG / DBL_MANT_DIG24 / 53Bit della mantissa
#include <limits.h>
#include <float.h>

// Pattern: controllare overflow prima dell'operazione
if (a > 0 && b > INT_MAX - a) {
    // a + b causerebbe overflow!
}

// Confronto float con epsilon
if (fabs(a - b) < DBL_EPSILON * fabs(a + b)) {
    // a e b sono "uguali" (relative epsilon)
}
time.h — DATA & TEMPO
Tipi
TipoDescrizione
time_tTempo calendariale (secondi da epoch, tipicamente 1970-01-01)
clock_tTempo di CPU (tick)
struct tmTempo scomposto (anno, mese, giorno, ora, min, sec, ecc.)
Funzioni Principali
FunzioneDescrizione
time(&t)Ottiene il tempo corrente (secondi da epoch)
clock()Tempo CPU usato dal programma (clock_t)
difftime(t1, t0)Differenza in secondi (double) tra due time_t
mktime(&tm)struct tm → time_t (normalizza i campi)
localtime(&t)time_t → struct tm* (ora locale)
gmtime(&t)time_t → struct tm* (UTC)
asctime(&tm)struct tm → stringa leggibile
ctime(&t)time_t → stringa leggibile (= asctime(localtime(&t)))
strftime(buf, max, fmt, &tm)Formattazione personalizzata in un buffer
#include <time.h>

// Ottenere data/ora corrente
time_t now = time(NULL);
struct tm *lt = localtime(&now);
printf("%d-%02d-%02d %02d:%02d:%02d\n",
       lt->tm_year + 1900, lt->tm_mon + 1, lt->tm_mday,
       lt->tm_hour, lt->tm_min, lt->tm_sec);

// Formattazione con strftime
char buf[64];
strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", lt);
puts(buf);   // "2024-03-15 14:30:45"

// Misurare tempo di esecuzione (CPU time)
clock_t start = clock();
// ... lavoro pesante ...
clock_t end = clock();
double cpu_sec = (double)(end - start) / CLOCKS_PER_SEC;
printf("CPU time: %.3f s\n", cpu_sec);

// Misurare tempo reale (wall time)
time_t t0 = time(NULL);
// ... lavoro ...
time_t t1 = time(NULL);
printf("Elapsed: %.0f s\n", difftime(t1, t0));
Campi di struct tm
CampoRangeDescrizione
tm_sec0–60Secondi (60 per leap second)
tm_min0–59Minuti
tm_hour0–23Ore
tm_mday1–31Giorno del mese
tm_mon0–11Mese (0 = gennaio)
tm_yearanni da 1900Anno (es. 124 = 2024)
tm_wday0–6Giorno della settimana (0 = domenica)
tm_yday0–365Giorno dell'anno
tm_isdst-1/0/1Ora legale (-1 = auto)
Specificatori strftime
Spec.EsempioDescrizione
%Y2024Anno a 4 cifre
%m03Mese (01-12)
%d15Giorno (01-31)
%H14Ora 24h (00-23)
%M30Minuti (00-59)
%S45Secondi (00-60)
%A / %aFriday / FriNome giorno
%B / %bMarch / MarNome mese
%ZCETTimezone
%s1710510645Epoch (estensione glibc)
volatile & restrict
volatile
// Impedisce al compilatore di ottimizzare l'accesso alla variabile
// Usato per: I/O mappato in memoria, signal handler, variabili condivise

volatile int *hw_reg = (volatile int *)0xFFFF0000;
while (*hw_reg == 0) { }   // il compilatore non elimina il loop

// Nei signal handler:
volatile sig_atomic_t flag = 0;
restrict (C99)
// Promessa al compilatore: il puntatore è l'unico modo di accedere a quell'area
// Permette ottimizzazioni più aggressive

void vec_add(int * restrict dst,
            const int * restrict a,
            const int * restrict b, size_t n) {
    for (size_t i = 0; i < n; i++)
        dst[i] = a[i] + b[i];  // il compilatore sa che non c'è aliasing
}

// memcpy usa restrict: src e dst non possono sovrapporsi
// memmove NON usa restrict: gestisce la sovrapposizione
INLINE FUNCTIONS (C99)
// Suggerisce al compilatore di inserire il corpo della funzione inline
static inline int max(int a, int b) {
    return (a > b) ? a : b;
}

// "inline" in C99 ha semantica diversa dal C++:
// - "inline" nell'header → solo definizione inline (nessun simbolo esterno)
// - Serve una definizione extern in un .c per generare il simbolo
// - "static inline" è il pattern più semplice e portabile
static inline nell'header è il modo più sicuro e portabile di usare inline in C99.
VARIABLE-LENGTH ARRAYS — VLA (C99)
void process(int n) {
    int arr[n];              // allocato sullo stack a runtime

    for (int i = 0; i < n; i++)
        arr[i] = i * i;
}

// VLA multidimensionale
void mat_zero(int rows, int cols, int m[rows][cols]) {
    for (int i = 0; i < rows; i++)
        for (int j = 0; j < cols; j++)
            m[i][j] = 0;
}
⚠ VLA allocato sullo stack: rischio stack overflow per n grande. Non usare sizeof su VLA a runtime per codice portabile. C11 rende VLA opzionale.
COMPOUND LITERALS (C99)
// Crea un oggetto anonimo del tipo specificato

// Passare struct temporanee a funzione
draw_point((struct Punto){3.0, 4.0});

// Array temporanei
int *p = (int[]){10, 20, 30};
printf("%d\n", p[1]);    // 20

// Con designated initializers
struct Config cfg = (struct Config){
    .width = 800,
    .height = 600,
    .fullscreen = false
};

// Lifetime: fino alla fine del blocco (come variabili auto)
DESIGNATED INITIALIZERS (C99)
// Array
int a[10] = {
    [0] = 1,
    [5] = 50,
    [9] = 99      // tutti gli altri = 0
};

// Struct — ordine libero
struct Punto p = {
    .y = 3.14,
    .x = 2.71
};

// Struct annidate
struct Rettangolo r = {
    .origine = {.x = 0, .y = 0},
    .larghezza = 100,
    .altezza = 50
};

// Array di struct
struct Punto pts[] = {
    [0] = {.x = 1, .y = 2},
    [2] = {.x = 5, .y = 6}    // [1] zero-inizializzato
};
ASSERT & DEBUGGING
#include <assert.h>

assert(ptr != NULL);          // se falso: stampa errore e abort()
assert(n > 0 && "n deve essere positivo");  // messaggio nella condizione

// Disabilitare assert in produzione:
// gcc -DNDEBUG main.c
// oppure:
#define NDEBUG
#include <assert.h>          // assert diventa no-op
Macro di Debug Personalizzata
#ifdef DEBUG
#define DBG(fmt, ...) \
    fprintf(stderr, "[DBG %s:%d] " fmt "\n", \
            __FILE__, __LINE__, ##__VA_ARGS__)
#else
#define DBG(fmt, ...) ((void)0)
#endif

DBG("valore di x = %d", x);
// Compilare con: gcc -DDEBUG ...
GESTIONE ERRORI (errno)
#include <errno.h>
#include <string.h>

FILE *f = fopen("/path/file", "r");
if (!f) {
    fprintf(stderr, "Errore %d: %s\n", errno, strerror(errno));
    // oppure:
    perror("fopen");   // stampa "fopen: No such file or directory"
    exit(EXIT_FAILURE);
}
CostanteDescrizione
EACCESPermesso negato
ENOENTFile o directory non trovato
ENOMEMMemoria insufficiente
EINVALArgomento non valido
EIOErrore I/O
ERANGERisultato fuori range (usato da strtol, ecc.)
EDOMArgomento fuori dominio matematico
Settare errno = 0 prima della chiamata. Controllare errno solo se la funzione ha segnalato errore (es. ritorno NULL o -1).
SETJMP / LONGJMP — SALTI NON LOCALI
#include <setjmp.h>

jmp_buf env;

void do_work(void) {
    // ... errore critico ...
    longjmp(env, 42);   // salta indietro a setjmp, restituisce 42
}

int main(void) {
    int val = setjmp(env);   // prima chiamata: ritorna 0
    if (val == 0) {
        do_work();             // esecuzione normale
    } else {
        printf("Errore catturato: %d\n", val);  // val = 42
    }
    return 0;
}
FunzioneDescrizione
setjmp(env)Salva il contesto di esecuzione. Ritorna 0 alla prima chiamata
longjmp(env, val)Ripristina il contesto; setjmp ritorna val (se val=0 → ritorna 1)
⚠ Le variabili locali modificate tra setjmp e longjmp hanno valore indefinito a meno che non siano volatile. Non usare longjmp dopo che la funzione contenente setjmp è ritornata.
Usato raramente: pattern try/catch rudimentale, error recovery in parser, gestione errori in librerie. Preferire valori di ritorno e goto per cleanup.
SCOPE & LINKAGE
Scope (Visibilità)
Tipo di ScopeDoveVisibilità
Block scopeVariabili dentro { }Dal punto di dichiarazione fino alla } di chiusura
File scopeDichiarazioni fuori da ogni funzioneDal punto di dichiarazione fino a fine file
Function scopeSolo le label di gotoTutta la funzione
Function prototype scopeNomi parametri nel prototipoSolo dentro le parentesi del prototipo
Linkage
LinkageSignificatoQuando
ExternalLo stesso nome in più unità di traduzione si riferisce allo stesso oggettoFunzioni e variabili globali (default)
InternalLo stesso nome in un'unità si riferisce allo stesso oggetto, ma invisibile fuoristatic a file scope
NoneOgni dichiarazione è un oggetto distintoVariabili locali, parametri
// External linkage — visibile da altri file
int g_visible = 10;              // definizione, external linkage
extern int g_visible;            // dichiarazione (in altro file/header)

// Internal linkage — visibile solo in questo file
static int s_private = 20;       // non accessibile da altri file
static void helper(void) {}    // funzione privata al file

// No linkage
void foo(void) {
    int x = 5;          // block scope, no linkage
    static int y = 0;  // block scope, no linkage, ma lifetime statico
}
Regola One Definition Rule (ODR)

Ogni variabile/funzione con external linkage deve avere esattamente una definizione in tutto il programma. Più dichiarazioni (extern) sono permesse.

Shadowing
int x = 10;                 // file scope
void foo(void) {
    int x = 20;             // nasconde la x globale
    {
        int x = 30;         // nasconde la x locale esterna
        printf("%d\n", x); // 30
    }
    printf("%d\n", x);     // 20
}
Usare -Wshadow per ricevere warning su variabili che ne nascondono altre.
BITWISE TRICKS
// Settare il bit n
x |= (1U << n);

// Cancellare il bit n
x &= ~(1U << n);

// Toggleare il bit n
x ^= (1U << n);

// Testare il bit n
if (x & (1U << n)) { /* bit è 1 */ }

// Controllare se x è potenza di 2
bool is_pow2 = x && !(x & (x - 1));

// Swap senza temp
a ^= b; b ^= a; a ^= b;

// Valore assoluto (per int, evita branch)
int mask = x >> 31;
int abs_x = (x + mask) ^ mask;

// Arrotondamento alla prossima potenza di 2
uint32_t next_pow2(uint32_t v) {
    v--;
    v |= v >> 1;  v |= v >> 2;
    v |= v >> 4;  v |= v >> 8;
    v |= v >> 16;
    return v + 1;
}

// Contare bit a 1 (popcount) — GCC built-in
int bits = __builtin_popcount(x);       // per unsigned int
int bits = __builtin_popcountll(x);     // per unsigned long long

// Contare zeri iniziali / finali
int lz = __builtin_clz(x);   // count leading zeros
int tz = __builtin_ctz(x);   // count trailing zeros

// Bit fields come mask
#define BIT(n)        (1U << (n))
#define BITS(h, l)    (((1U << ((h)-(l)+1)) - 1) << (l))
UNDEFINED BEHAVIOR (UB)

Il comportamento indefinito (UB) permette al compilatore di assumere che non accada mai. Il codice con UB può funzionare, crashare, o produrre risultati arbitrari — anche in modo diverso con livelli di ottimizzazione diversi.

Casistica Principale
UBEsempioCosa succede
Signed integer overflowINT_MAX + 1Il compilatore può eliminare branch, loop, ecc.
Null pointer deref*((int*)NULL)Crash o comportamento imprevedibile
Accesso fuori boundsarr[10] su arr[5]Corruzione memoria, crash, silenzioso
Use after freefree(p); *p = 1;Corruzione heap
Double freefree(p); free(p);Corruzione allocatore
Variabile non inizializzataint x; printf("%d", x);Valore indeterminato (non "garbage")
Modifica di string literalchar *s = "hi"; s[0] = 'H';Segfault tipico
Divisione per zerox / 0UB (non solo eccezione)
Shift eccessivo1 << 32 (su int 32bit)UB, anche con unsigned
Shift negativo1 << -1UB
Violazione strict aliasingCast int* a float* e derefOttimizzatore assume tipi distinti
Sequence point violationi = i++ + ++i;Ordine valutazione indefinito
Ritorno senza valoreFunzione non-void senza returnValore di ritorno indeterminato
Modifica constCast via const e modificaUB se l'oggetto è veramente const
memcpy overlapmemcpy(p, p+2, 10)Usare memmove per aree sovrapposte
Strumenti di Rilevamento
# Sanitizer a compile-time (GCC/Clang)
gcc -fsanitize=undefined -fsanitize=address -g main.c
# UBSan: signed overflow, shift, null deref, alignment, ecc.
# ASan: buffer overflow, use-after-free, double free, leak

# Valgrind (a runtime, no ricompilazione)
valgrind --leak-check=full --track-origins=yes ./app

# Flag warning utili
gcc -Wall -Wextra -Wpedantic -Wshadow -Wconversion \
    -Wstrict-aliasing=2 -Wformat=2 -Wnull-dereference
Overflow Signed — Come Evitare
// ❌ UB: signed overflow
if (a + b > INT_MAX) { ... }   // l'overflow è già avvenuto!

// ✓ Controllare PRIMA dell'operazione
if (b > 0 && a > INT_MAX - b) { /* overflow */ }
if (b < 0 && a < INT_MIN - b) { /* underflow */ }

// ✓ Usare unsigned per operazioni dove l'overflow è intenzionale
uint32_t hash = (hash * 31) + c;  // overflow unsigned = wrap-around (definito)

// ✓ GCC/Clang built-in
int result;
if (__builtin_add_overflow(a, b, &result)) {
    // overflow rilevato
}
STRICT ALIASING

La regola di strict aliasing dice che un oggetto di tipo T può essere acceduto solo tramite puntatori a: T, T con qualificatori (const T, volatile T), un tipo firmato/non firmato compatibile, un tipo aggregato che contiene T, o char/unsigned char.

Violazione — Tipo Punning Illegale
// ❌ UNDEFINED BEHAVIOR — strict aliasing violation
float f = 3.14f;
int bits = *(int *)&f;      // UB! float* → int* deref

// ❌ Anche questo è UB
int i = 42;
short *sp = (short *)&i;
*sp = 7;                     // UB! int* → short* write
Alternative Legali per Type Punning
// ✓ Metodo 1: memcpy (il più portabile e sicuro)
float f = 3.14f;
uint32_t bits;
memcpy(&bits, &f, sizeof(bits));   // OK! nessuna violazione

// ✓ Metodo 2: union (definito in C99, UB in C++)
union {
    float    f;
    uint32_t u;
} pun;
pun.f = 3.14f;
uint32_t bits = pun.u;          // OK in C99 (type punning via union)

// ✓ Metodo 3: accesso via char* (sempre legale)
float f = 3.14f;
unsigned char *bytes = (unsigned char *)&f;
for (size_t i = 0; i < sizeof(f); i++)
    printf("%02x ", bytes[i]);   // OK! char* può aliasare qualunque tipo
Per disabilitare strict aliasing: gcc -fno-strict-aliasing. Il kernel Linux compila con questo flag.
OPAQUE TYPES & ABSTRACT DATA TYPES

Pattern per incapsulamento in C: l'utente vede solo un puntatore; i dettagli interni sono nascosti nel .c.

/* ─── stack.h (interfaccia pubblica) ─── */
#ifndef STACK_H
#define STACK_H

#include <stddef.h>
#include <stdbool.h>

// Tipo opaco: l'utente non conosce la struttura interna
typedef struct Stack Stack;

Stack *stack_create(size_t capacity);
void   stack_destroy(Stack *s);
bool   stack_push(Stack *s, int val);
bool   stack_pop(Stack *s, int *out);
size_t stack_size(const Stack *s);
bool   stack_empty(const Stack *s);

#endif
/* ─── stack.c (implementazione privata) ─── */
#include "stack.h"
#include <stdlib.h>

struct Stack {
    int    *data;
    size_t  top;
    size_t  capacity;
};

Stack *stack_create(size_t capacity) {
    Stack *s = malloc(sizeof(*s));
    if (!s) return NULL;
    s->data = malloc(capacity * sizeof(*s->data));
    if (!s->data) { free(s); return NULL; }
    s->top = 0;
    s->capacity = capacity;
    return s;
}

void stack_destroy(Stack *s) {
    if (s) { free(s->data); free(s); }
}

bool stack_push(Stack *s, int val) {
    if (s->top >= s->capacity) return false;
    s->data[s->top++] = val;
    return true;
}

bool stack_pop(Stack *s, int *out) {
    if (s->top == 0) return false;
    *out = s->data[--s->top];
    return true;
}

size_t stack_size(const Stack *s) { return s->top; }
bool   stack_empty(const Stack *s) { return s->top == 0; }
/* ─── main.c (l'utente non vede struct Stack) ─── */
#include "stack.h"

Stack *s = stack_create(100);
stack_push(s, 42);
int val;
stack_pop(s, &val);
stack_destroy(s);
Vantaggi: modifica interna senza ricompilare il codice client, nascondere dettagli implementativi, impedire accesso diretto ai campi. Svantaggio: ogni oggetto è allocato nello heap.
X-MACROS

Tecnica per generare codice ripetitivo da un'unica tabella di definizioni. Elimina la duplicazione e previene disallineamenti.

// Definire la tabella una sola volta
#define ERROR_TABLE(X) \
    X(ERR_NONE,     "Nessun errore") \
    X(ERR_IO,       "Errore I/O") \
    X(ERR_MEMORY,   "Memoria esaurita") \
    X(ERR_PARSE,    "Errore di parsing") \
    X(ERR_TIMEOUT,  "Timeout")

// Generare l'enum automaticamente
enum ErrorCode {
#define X_ENUM(code, msg) code,
    ERROR_TABLE(X_ENUM)
#undef X_ENUM
    ERR_COUNT   // totale errori
};

// Generare la tabella stringhe automaticamente
static const char *error_strings[] = {
#define X_STR(code, msg) [code] = msg,
    ERROR_TABLE(X_STR)
#undef X_STR
};

// Uso
printf("Errore: %s\n", error_strings[ERR_MEMORY]);
// → "Errore: Memoria esaurita"
Aggiungere un nuovo errore richiede modifica in un solo punto (la tabella). L'enum e le stringhe restano automaticamente sincronizzati.
ERROR HANDLING PATTERNS
Pattern 1: Return Code + errno
// La funzione ritorna 0 = successo, -1 = errore (setta errno)
int read_config(const char *path, struct Config *out) {
    FILE *f = fopen(path, "r");
    if (!f) return -1;     // errno già settato da fopen
    // ...
    fclose(f);
    return 0;
}
Pattern 2: Enum Error Code
typedef enum {
    RESULT_OK = 0,
    RESULT_ERR_NULL,
    RESULT_ERR_RANGE,
    RESULT_ERR_IO,
} Result;

Result parse_int(const char *s, int *out) {
    if (!s)   return RESULT_ERR_NULL;
    char *end;
    long val = strtol(s, &end, 10);
    if (*end != '\0') return RESULT_ERR_RANGE;
    *out = (int)val;
    return RESULT_OK;
}
Pattern 3: Output via Puntatore, Errore nel Return
// La funzione ritorna il puntatore (NULL = errore)
char *read_file(const char *path, size_t *out_len) {
    FILE *f = fopen(path, "rb");
    if (!f) return NULL;

    fseek(f, 0, SEEK_END);
    long len = ftell(f);
    rewind(f);

    char *buf = malloc(len + 1);
    if (!buf) { fclose(f); return NULL; }

    fread(buf, 1, len, f);
    buf[len] = '\0';
    fclose(f);

    if (out_len) *out_len = len;
    return buf;   // il chiamante deve fare free()
}
Pattern 4: goto Cleanup (Multi-risorsa)
int process(const char *path) {
    int ret = -1;
    FILE *f = NULL;
    char *buf = NULL;

    f = fopen(path, "r");
    if (!f) goto out;

    buf = malloc(4096);
    if (!buf) goto out;

    // ... lavoro con f e buf ...
    ret = 0;   // successo

out:
    free(buf);     // free(NULL) è safe
    if (f) fclose(f);
    return ret;
}
STRUTTURE DATI GENERICHE IN C
Approccio 1: void* (Runtime Generics)
// Lista generica con void*
typedef struct {
    void   **items;
    size_t   count;
    size_t   capacity;
} Vec;

void vec_push(Vec *v, void *item) {
    if (v->count >= v->capacity) {
        v->capacity = v->capacity ? v->capacity * 2 : 8;
        v->items = realloc(v->items, v->capacity * sizeof(void *));
    }
    v->items[v->count++] = item;
}

// Uso — nessuna type-safety
Vec v = {0};
int x = 42;
vec_push(&v, &x);
int *p = (int *)v.items[0];
Approccio 2: Copia Byte con memcpy
// Vettore che copia gli elementi per valore
typedef struct {
    char   *data;       // buffer di byte
    size_t  elem_size;  // sizeof di un elemento
    size_t  count;
    size_t  capacity;
} GVec;

void gvec_push(GVec *v, const void *elem) {
    if (v->count >= v->capacity) {
        v->capacity = v->capacity ? v->capacity * 2 : 8;
        v->data = realloc(v->data, v->capacity * v->elem_size);
    }
    memcpy(v->data + v->count * v->elem_size, elem, v->elem_size);
    v->count++;
}

void *gvec_get(const GVec *v, size_t i) {
    return v->data + i * v->elem_size;
}

// Uso
GVec v = {.elem_size = sizeof(double)};
double val = 3.14;
gvec_push(&v, &val);
double *p = (double *)gvec_get(&v, 0);
Approccio 3: Macro Type-Safe (Compile-time Generics)
// Genera un tipo vettore specifico per ogni tipo
#define DEFINE_VEC(T, Name) \
    typedef struct { \
        T      *data; \
        size_t  count; \
        size_t  capacity; \
    } Name; \
    \
    static inline void Name##_push(Name *v, T val) { \
        if (v->count >= v->capacity) { \
            v->capacity = v->capacity ? v->capacity * 2 : 8; \
            v->data = realloc(v->data, v->capacity * sizeof(T)); \
        } \
        v->data[v->count++] = val; \
    } \
    static inline T Name##_get(Name *v, size_t i) { \
        return v->data[i]; \
    }

// Istanziare per i tipi necessari
DEFINE_VEC(int,    IntVec)
DEFINE_VEC(double, DoubleVec)

// Uso — completamente type-safe
IntVec v = {0};
IntVec_push(&v, 42);
int x = IntVec_get(&v, 0);   // type-safe, nessun cast
Approccio 4: Intrusive Linked List (stile kernel)
// Il nodo di lista è DENTRO la struttura dell'utente
struct list_node {
    struct list_node *next;
    struct list_node *prev;
};

// Macro per risalire al contenitore
#define container_of(ptr, type, member) \
    ((type *)((char *)(ptr) - offsetof(type, member)))

// Struttura utente che CONTIENE il nodo
struct Task {
    int              id;
    char             name[32];
    struct list_node link;    // embedded
};

// Risalire da list_node a Task
struct list_node *n = /* ... */;
struct Task *t = container_of(n, struct Task, link);
container_of e le liste intrusive sono usati estensivamente nel kernel Linux e in molti progetti C ad alte prestazioni. Vantaggi: nessun'allocazione extra per il nodo, un oggetto può stare in più liste contemporaneamente.
Confronto Approcci
ApproccioType-safetyOverheadComplessità
void*NessunaIndirezione puntatoreBassa
memcpy + elem_sizeNessunaCopia byteMedia
Macro generativeCompletaZero (inline)Alta (debug macro)
Intrusive listVia container_ofZero alloc extraMedia