Gestione della memoria in C
In questa pagina 5
Organizzazione della memoria di un processo
| Segmento | Contenuto | Gestione | Durata |
|---|---|---|---|
| Codice (text) | istruzioni macchina | caricato dal sistema operativo | intero programma |
| Dati statici | variabili globali e static, letterali stringa |
allocati all'avvio, inizializzati a zero se non indicato | intero programma |
| Stack | record di attivazione: parametri, variabili locali, indirizzo di ritorno | automatica: cresce a ogni chiamata, si libera al ritorno | fino alla fine della funzione/blocco |
| Heap | memoria allocata dinamicamente | manuale: malloc / free |
finché non si chiama free |
Lo stack è piccolo (tipicamente qualche MB): array locali molto grandi o ricorsione troppo profonda causano stack overflow. L'heap è grande quanto la memoria disponibile.
Perché l'allocazione dinamica
Serve quando:
- la dimensione è nota solo a run-time (array di
nelementi connletto da input); - un dato deve sopravvivere alla funzione che lo crea (ad esempio i nodi di una lista concatenataLista concatenata semplice: nodi allocati dinamicamente collegati da puntatori; inserimento e rimozione in testa, scorrimento, ricerca, inserimento ordinato, deallocazione; confronto dei costi con l'array; versione in Python.Liste concatenate →);
- una struttura deve crescere (array dinamico, come le liste di Python).
malloc, calloc, realloc, free
Funzioni di <stdlib.h>:
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int n;
if (scanf("%d", &n) != 1 || n <= 0) return 1;
int *v = malloc(n * sizeof(int)); /* n interi, contenuto indeterminato */
if (v == NULL) { /* memoria esaurita */
fprintf(stderr, "allocazione fallita\n");
return 1;
}
for (int i = 0; i < n; i++)
v[i] = i * i; /* si usa come un array */
free(v); /* restituisce la memoria */
v = NULL; /* evita usi successivi per errore */
return 0;
}| Funzione | Effetto |
|---|---|
malloc(byte) |
alloca byte byte non inizializzati; restituisce void * o NULL |
calloc(n, dim) |
alloca n elementi di dim byte, azzerati |
realloc(p, nuovi_byte) |
ridimensiona il blocco; può spostarlo e restituire un nuovo indirizzo (copiando il contenuto); NULL se fallisce, nel qual caso il vecchio blocco resta valido |
free(p) |
libera un blocco ottenuto da malloc/calloc/realloc; free(NULL) non fa niente |
- Calcolare sempre la dimensione con
sizeof:malloc(n * sizeof *v)resta corretto anche se cambia il tipo div. - Ogni
mallocriuscita deve avere esattamente unfreecorrispondente.
Array che cresce (come list.append)
int cap = 4, n = 0;
int *v = malloc(cap * sizeof *v);
int x;
while (scanf("%d", &x) == 1) {
if (n == cap) {
cap *= 2; /* raddoppio: append O(1) ammortizzato */
int *tmp = realloc(v, cap * sizeof *v);
if (tmp == NULL) { free(v); return 1; }
v = tmp;
}
v[n++] = x;
}
free(v);Raddoppiando la capacità, il costo totale delle copie su n inserimenti è : è lo stesso meccanismo delle Liste in Pythonlist come sequenza mutabile (array dinamico); indici, slicing e assegnamento a fette; metodi e loro costo; aliasing, copia superficiale e profonda; list comprehension; liste annidate e matrici.Liste in Python →.
Restituire memoria da una funzione
int *crea_sbagliato(void) {
int locale[100];
return locale; /* SBAGLIATO: locale sparisce al ritorno (dangling pointer) */
}
int *crea_array(int n) {
int *v = malloc(n * sizeof *v); /* corretto: vive nell'heap */
return v; /* il chiamante dovrà fare free */
}
char *duplica(const char *s) {
char *c = malloc(strlen(s) + 1); /* +1 per '\0' */
if (c != NULL)
strcpy(c, s);
return c;
}Va documentato chi è responsabile del free (di solito il chiamante).
Errori classici
| Errore | Che cosa succede |
|---|---|
| Memory leak | si perde l'ultimo puntatore a un blocco senza free: memoria occupata fino alla fine del processo |
| Dangling pointer | uso di un puntatore a memoria già liberata o a una variabile locale di una funzione terminata |
| Double free | free due volte sullo stesso blocco: corruzione dell'heap |
| Buffer overflow | scrittura oltre la fine del blocco allocato |
| Memoria non inizializzata | lettura di un blocco da malloc prima di averlo scritto |
free di memoria non dinamica |
free su un array locale o su un puntatore spostato a metà blocco |
p = realloc(p, ...) |
se realloc fallisce p diventa NULL e il blocco originale è perso (leak) |
Nessuno di questi errori è segnalato dal compilatore e spesso il programma sembra funzionare. Strumenti: valgrind --leak-check=full ./prog (Linux), opzione -fsanitize=address di gcc/clang (vedi Debugging e testingTipi di errore, metodo per localizzare un bug, lettura del traceback, stampe di controllo, debugger (VS Code, pdb, gdb), assert e strumenti per la memoria in C; test di unità, casi limite e partizioni, doctest e unittest.Debugging e testing →).
Confronto con Python
In Python ogni oggetto vive nell'heap e il garbage collector lo libera quando non è più raggiungibile (Variabili, oggetti e riferimentiIn Python una variabile è un nome legato a un oggetto; tipo, identità e valore; assegnamento, aliasing, oggetti mutabili e immutabili, None.Variabili, oggetti e riferimenti →): leak "classici", dangling pointer e double free non possono capitare. Il prezzo è il controllo e un costo in tempo e memoria.