# 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 | Descrizione |
|---|---|
-std=c99 | Standard C99 |
-Wall | Abilita la maggior parte dei warning |
-Wextra | Warning extra oltre -Wall |
-Werror | Tratta i warning come errori |
-pedantic | Aderenza stretta allo standard |
-g | Informazioni di debug (per GDB) |
-O0 / -O1 / -O2 / -O3 / -Os | Livelli di ottimizzazione |
-D NOME=val | Definisce macro da riga di comando |
-I dir | Aggiunge directory per header include |
-L dir | Aggiunge directory per librerie |
-fsanitize=address | AddressSanitizer (buffer overflow, use-after-free) |
-fsanitize=undefined | UBSan (comportamento indefinito) |
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
#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; }
/* myheader.h */ #ifndef MYHEADER_H #define MYHEADER_H int somma(int a, int b); void print_info(const char *msg); #endif /* MYHEADER_H */
Ogni file .c + i suoi #include forma un'unità di traduzione compilata indipendentemente. Il linker unisce i file .o nel binario finale.
| Tipo | Dimensione tipica | Range |
|---|---|---|
char | 1 byte | -128 .. 127 (o 0..255 se unsigned) |
short | 2 byte | -32768 .. 32767 |
int | 4 byte | -2³¹ .. 2³¹-1 |
long | 4/8 byte | almeno 32 bit |
long long | 8 byte | -2⁶³ .. 2⁶³-1 (C99) |
_Bool | 1 byte | 0 o 1 (C99) |
Ogni tipo intero può essere signed (default per int/short/long) o unsigned.
| Tipo | Dimensione | Precisione |
|---|---|---|
float | 4 byte | ~7 cifre decimali |
double | 8 byte | ~15 cifre decimali |
long double | 10/12/16 byte | ≥ double (impl. defined) |
void — tipo incompleto: nessun valore. Usato per funzioni senza ritorno, puntatori generici (void *), e cast espliciti.
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
| Qualificatore | Significato |
|---|---|
const | Valore non modificabile dopo l'inizializzazione |
volatile | Valore può cambiare esternamente (HW, signal handler) |
restrict | Il puntatore è l'unico accesso a quell'area di memoria (C99) |
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)
'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
#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)
| Prec. | Operatori | Associatività |
|---|---|---|
| 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 |
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
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)
a += b a -= b a *= b a /= b a %= b a &= b a |= b a ^= b a <<= b a >>= b
int x = 5; int a = x++; // a = 5, poi x = 6 (post-incremento) int b = ++x; // x = 7, poi b = 7 (pre-incremento)
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
Tipi più piccoli di int (char, short, _Bool, bitfield) vengono promossi a int (o unsigned int) prima di qualsiasi operazione aritmetica.
// 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
-1 < 1U è falso perché -1 diventa UINT_MAX. Usare -Wsign-compare.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)
if (x > 0) { printf("positivo\n"); } else if (x == 0) { printf("zero\n"); } else { printf("negativo\n"); }
switch (scelta) { case 1: printf("uno\n"); break; case 2: case 3: printf("due o tre\n"); break; default: printf("altro\n"); break; }
break si ha fall-through.// 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);
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;
// 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"!// 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
// 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
// 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; }
#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
| Macro | Descrizione |
|---|---|
va_list | Tipo 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) |
// 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); }
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 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
const si applica a ciò che sta alla sua sinistra. Se non c'è nulla a sinistra, si applica a destra.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++)
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};
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)
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).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 */ }
// 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]); }
#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);
// 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')
| Funzione | Descrizione |
|---|---|
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 |
// 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()
// 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 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)
// 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 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 */ }
// 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; };
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
| Specifier | Scope | Lifetime | Note |
|---|---|---|---|
auto | Blocco | Blocco | Default per variabili locali (mai usato esplicitamente) |
register | Blocco | Blocco | Suggerimento al compilatore (ignorato con -O). No & operator |
static (locale) | Blocco | Programma | Persiste tra le chiamate, inizializzata a zero se non esplicita |
static (globale/fn) | File | Programma | Linkage interno: visibile solo nell'unità di traduzione |
extern | Globale | Programma | Dichiara una variabile/funzione definita altrove |
| (nessuno, globale) | Globale | Programma | Linkage esterno (visibile da altre unità di traduzione) |
/* 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
| Funzione | Descrizione |
|---|---|
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
// 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]
| Direttiva | Descrizione |
|---|---|
#include <h> | Include header di sistema |
#include "h" | Include header locale (cerca prima nella dir corrente) |
#define NAME val | Definisce macro oggetto |
#define M(a,b) ... | Definisce macro funzione |
#undef NAME | Rimuove la definizione di una macro |
#if expr | Compilazione condizionale |
#ifdef NAME | Se NAME è definita |
#ifndef NAME | Se NAME non è definita |
#elif expr | Else if |
#else | Else |
#endif | Fine blocco condizionale |
#pragma | Direttiva specifica del compilatore |
#error "msg" | Genera errore di compilazione |
#line n "file" | Cambia numero riga e nome file |
#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 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 | Descrizione |
|---|---|
__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
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
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
gets() — buffer overflow garantito. Usare fgets().| Stream | Tipo | Descrizione |
|---|---|---|
stdin | FILE* | Standard input (fd 0) |
stdout | FILE* | Standard output (fd 1) — buffered |
stderr | FILE* | Standard error (fd 2) — unbuffered |
| 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);
| Funzione | Descrizione |
|---|---|
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) |
| Spec. | Tipo | Descrizione |
|---|---|---|
%d / %i | int | Intero decimale con segno |
%u | unsigned | Intero decimale senza segno |
%o | unsigned | Ottale |
%x / %X | unsigned | Esadecimale min/maiuscolo |
%f | double | Virgola mobile (default 6 decimali) |
%e / %E | double | Notazione scientifica |
%g / %G | double | Sceglie %f o %e (il più corto) |
%c | int | Carattere |
%s | char* | Stringa |
%p | void* | Puntatore (indirizzo) |
%n | int* | Scrive il numero di char scritti finora |
%% | — | Stampa un % letterale |
%zu | size_t | size_t (C99) |
%lld | long long | long long (C99) |
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
| Funzione | Descrizione |
|---|---|
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 / lldiv | Quoziente 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 |
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);
Compilare con -lm per linkare la libreria matematica.
| Funzione | Descrizione |
|---|---|
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 / tanh | Iperboliche |
hypot(x, y) | √(x² + y²) (C99) |
INFINITY / NAN | Costanti speciali (C99) |
isnan(x) / isinf(x) / isfinite(x) | Test valori speciali (C99) |
| Funzione | True 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 |
unsigned char o EOF. Passare char con valori negativi è UB.| Tipo | Bit | Descrizione |
|---|---|---|
int8_t / uint8_t | 8 | Esattamente 8 bit |
int16_t / uint16_t | 16 | Esattamente 16 bit |
int32_t / uint32_t | 32 | Esattamente 32 bit |
int64_t / uint64_t | 64 | Esattamente 64 bit |
intptr_t / uintptr_t | ptr | Capace di contenere un puntatore |
size_t | — | Risultato di sizeof (unsigned, stddef.h) |
ptrdiff_t | — | Differenza tra puntatori (signed, stddef.h) |
intmax_t / uintmax_t | max | Il tipo intero più grande supportato |
#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
#include <stdbool.h> bool flag = true; if (flag) { /* ... */ } // bool, true, false sono macro per _Bool, 1, 0
#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
#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
| Header | Contenuto |
|---|---|
<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 |
| Costante | Valore tipico | Descrizione |
|---|---|---|
CHAR_BIT | 8 | Bit per byte |
CHAR_MIN / CHAR_MAX | -128 / 127 | Range di char (signedness impl-defined) |
SCHAR_MIN / SCHAR_MAX | -128 / 127 | signed char |
UCHAR_MAX | 255 | unsigned char |
SHRT_MIN / SHRT_MAX | -32768 / 32767 | short |
USHRT_MAX | 65535 | unsigned short |
INT_MIN / INT_MAX | -2³¹ / 2³¹-1 | int |
UINT_MAX | 2³²-1 | unsigned int |
LONG_MIN / LONG_MAX | almeno ±2³¹ | long (4 o 8 byte) |
ULONG_MAX | almeno 2³²-1 | unsigned long |
LLONG_MIN / LLONG_MAX | -2⁶³ / 2⁶³-1 | long long (C99) |
ULLONG_MAX | 2⁶⁴-1 | unsigned long long (C99) |
| Costante | Valore tipico (double) | Descrizione |
|---|---|---|
FLT_RADIX | 2 | Base dell'esponente |
FLT_DIG / DBL_DIG / LDBL_DIG | 6 / 15 / 18 | Cifre decimali di precisione |
FLT_EPSILON / DBL_EPSILON | 1.19e-7 / 2.22e-16 | Più piccolo ε t.c. 1.0 + ε ≠ 1.0 |
FLT_MIN / DBL_MIN | 1.17e-38 / 2.22e-308 | Più piccolo positivo normalizzato |
FLT_MAX / DBL_MAX | 3.4e38 / 1.8e308 | Valore massimo |
FLT_MIN_EXP / FLT_MAX_EXP | -125 / 128 | Esponente min/max (base 2) |
FLT_MANT_DIG / DBL_MANT_DIG | 24 / 53 | Bit 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) }
| Tipo | Descrizione |
|---|---|
time_t | Tempo calendariale (secondi da epoch, tipicamente 1970-01-01) |
clock_t | Tempo di CPU (tick) |
struct tm | Tempo scomposto (anno, mese, giorno, ora, min, sec, ecc.) |
| Funzione | Descrizione |
|---|---|
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));
| Campo | Range | Descrizione |
|---|---|---|
tm_sec | 0–60 | Secondi (60 per leap second) |
tm_min | 0–59 | Minuti |
tm_hour | 0–23 | Ore |
tm_mday | 1–31 | Giorno del mese |
tm_mon | 0–11 | Mese (0 = gennaio) |
tm_year | anni da 1900 | Anno (es. 124 = 2024) |
tm_wday | 0–6 | Giorno della settimana (0 = domenica) |
tm_yday | 0–365 | Giorno dell'anno |
tm_isdst | -1/0/1 | Ora legale (-1 = auto) |
| Spec. | Esempio | Descrizione |
|---|---|---|
%Y | 2024 | Anno a 4 cifre |
%m | 03 | Mese (01-12) |
%d | 15 | Giorno (01-31) |
%H | 14 | Ora 24h (00-23) |
%M | 30 | Minuti (00-59) |
%S | 45 | Secondi (00-60) |
%A / %a | Friday / Fri | Nome giorno |
%B / %b | March / Mar | Nome mese |
%Z | CET | Timezone |
%s | 1710510645 | Epoch (estensione glibc) |
// 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;
// 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
// 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.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; }
sizeof su VLA a runtime per codice portabile. C11 rende VLA opzionale.// 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)
// 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 };
#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
#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 ...
#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); }
| Costante | Descrizione |
|---|---|
EACCES | Permesso negato |
ENOENT | File o directory non trovato |
ENOMEM | Memoria insufficiente |
EINVAL | Argomento non valido |
EIO | Errore I/O |
ERANGE | Risultato fuori range (usato da strtol, ecc.) |
EDOM | Argomento fuori dominio matematico |
errno = 0 prima della chiamata. Controllare errno solo se la funzione ha segnalato errore (es. ritorno NULL o -1).#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; }
| Funzione | Descrizione |
|---|---|
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) |
volatile. Non usare longjmp dopo che la funzione contenente setjmp è ritornata.| Tipo di Scope | Dove | Visibilità |
|---|---|---|
| Block scope | Variabili dentro { } | Dal punto di dichiarazione fino alla } di chiusura |
| File scope | Dichiarazioni fuori da ogni funzione | Dal punto di dichiarazione fino a fine file |
| Function scope | Solo le label di goto | Tutta la funzione |
| Function prototype scope | Nomi parametri nel prototipo | Solo dentro le parentesi del prototipo |
| Linkage | Significato | Quando |
|---|---|---|
| External | Lo stesso nome in più unità di traduzione si riferisce allo stesso oggetto | Funzioni e variabili globali (default) |
| Internal | Lo stesso nome in un'unità si riferisce allo stesso oggetto, ma invisibile fuori | static a file scope |
| None | Ogni dichiarazione è un oggetto distinto | Variabili 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 }
Ogni variabile/funzione con external linkage deve avere esattamente una definizione in tutto il programma. Più dichiarazioni (extern) sono permesse.
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 }
-Wshadow per ricevere warning su variabili che ne nascondono altre.// 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))
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.
| UB | Esempio | Cosa succede |
|---|---|---|
| Signed integer overflow | INT_MAX + 1 | Il compilatore può eliminare branch, loop, ecc. |
| Null pointer deref | *((int*)NULL) | Crash o comportamento imprevedibile |
| Accesso fuori bounds | arr[10] su arr[5] | Corruzione memoria, crash, silenzioso |
| Use after free | free(p); *p = 1; | Corruzione heap |
| Double free | free(p); free(p); | Corruzione allocatore |
| Variabile non inizializzata | int x; printf("%d", x); | Valore indeterminato (non "garbage") |
| Modifica di string literal | char *s = "hi"; s[0] = 'H'; | Segfault tipico |
| Divisione per zero | x / 0 | UB (non solo eccezione) |
| Shift eccessivo | 1 << 32 (su int 32bit) | UB, anche con unsigned |
| Shift negativo | 1 << -1 | UB |
| Violazione strict aliasing | Cast int* a float* e deref | Ottimizzatore assume tipi distinti |
| Sequence point violation | i = i++ + ++i; | Ordine valutazione indefinito |
| Ritorno senza valore | Funzione non-void senza return | Valore di ritorno indeterminato |
| Modifica const | Cast via const e modifica | UB se l'oggetto è veramente const |
| memcpy overlap | memcpy(p, p+2, 10) | Usare memmove per aree sovrapposte |
# 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
// ❌ 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 }
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.
// ❌ 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
// ✓ 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
gcc -fno-strict-aliasing. Il kernel Linux compila con questo flag.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);
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"
// 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; }
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; }
// 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() }
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; }
// 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];
// 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);
// 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
// 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.| Approccio | Type-safety | Overhead | Complessità |
|---|---|---|---|
| void* | Nessuna | Indirezione puntatore | Bassa |
| memcpy + elem_size | Nessuna | Copia byte | Media |
| Macro generative | Completa | Zero (inline) | Alta (debug macro) |
| Intrusive list | Via container_of | Zero alloc extra | Media |