Gestione della memoria in C
In questa pagina 5
In questa pagina 4
Organizzazione della memoria di un processo
Definizione (segmenti di 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 stringaallocati 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/freefinché non si chiama free
Esempio. Una variabile locale vive nello stack, un blocco ottenuto con malloc nello heap e una variabile globale nei dati statici.
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;
}Definizione (malloc, calloc, realloc e free)
Funzione Effetto malloc(byte)alloca bytebyte non inizializzati; restituiscevoid *oNULLcalloc(n, dim)alloca nelementi didimbyte, azzeratirealloc(p, nuovi_byte)ridimensiona il blocco; può spostarlo e restituire un nuovo indirizzo (copiando il contenuto); NULLse fallisce, nel qual caso il vecchio blocco resta validofree(p)libera un blocco ottenuto da malloc/calloc/realloc;free(NULL)non fa niente
Esempio. malloc(4 * sizeof(int)) riserva 16 byte non inizializzati; calloc(4, sizeof(int)) riserva gli stessi 16 byte azzerati, quindi v[2] == 0.
- 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);Proprietà (array che cresce raddoppiando la capacità). Raddoppiando la capacità, il costo totale delle copie su
ninserimenti è : è 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 →.
Esempio. Partendo da capacità e raddoppiando, inserimenti copiano in tutto elementi.
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.
Versione ripasso
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 | fino alla fine della funzione/blocco |
| Heap | memoria allocata dinamicamente | manuale: malloc / free |
finché non si chiama free |
Stack piccolo (rischio stack overflow), heap grande quanto la memoria disponibile. Allocazione dinamica utile per dimensioni a run-time, persistenza oltre la funzione (es. 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 →) e strutture dinamiche.
malloc, calloc, realloc, free
Funzioni di <stdlib.h>:
| 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 (copiando); NULL se fallisce (il vecchio resta valido) |
free(p) |
libera un blocco ottenuto da allocazione dinamica; free(NULL) non fa niente |
- Usare sempre
sizeof:malloc(n * sizeof *v). - Ogni
mallocriuscita richiede un solofree.
Array che cresce
Quando n == cap si raddoppia la capacità con tmp = realloc(v, 2 * cap * sizeof *v), controllando tmp == NULL prima di assegnare v = tmp. Il costo totale delle copie su inserimenti è (come le 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
I puntatori locali non vanno restituiti (diventano dangling pointer). Vanno allocati nell'heap (malloc) e il chiamante dovrà fare free.
Errori classici
| Errore | Che cosa succede |
|---|---|
| Memory leak | perdita del puntatore senza free: memoria occupata fino alla fine del processo |
| Dangling pointer | uso di un puntatore a memoria già liberata o a variabile locale terminata |
| Double free | free eseguita due volte sullo stesso blocco: corruzione dell'heap |
| Buffer overflow | scrittura oltre la fine del blocco allocato |
| Memoria non inizializzata | lettura da malloc prima della scrittura |
p = realloc(p, ...) |
se fallisce, p diventa NULL perdendo il vecchio indirizzo (leak) |
Nessun errore è segnalato dal compilatore. Strumenti: valgrind --leak-check=full ./prog o -fsanitize=address (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 il garbage collector libera automaticamente gli oggetti nell'heap (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 e dangling pointer non esistono, al prezzo di overhead in tempo e memoria.
Esercizi su questo argomento
Teoria collegata
- Alberi e heap in C
- Algoritmi ricorsivi
- Heap
- Liste concatenate in C
- Puntatori, struct e memoria dinamica in C
- Visite di grafi - BFS e DFS
- Array e stringhe in C
- Dal Python al C
- Debugging e testing
- Funzioni in C
- Liste concatenate
- Ordinare e cercare in C - qsort, bsearch e puntatori a funzione
- Pila e coda
- Puntatori in C
- Realizzare contenitori su array e liste
- Ricorsione
- Struttura di un elaboratore
- Strutture (struct) in C
- Tipi di dato astratti
- Variabili, oggetti e riferimenti
- C++ - allocazione dinamica della memoria con new e delete
- C++ - chiamate a funzione e layout della memoria
- C++ - costruttori, inizializzazione, copia e spostamento
- Java - generics, reflection e passaggio dei parametri
- Memoria centrale - allocazione contigua, paginazione e segmentazione
- Memoria virtuale - paginazione su richiesta e sostituzione delle pagine
- Processi - stati, PCB, creazione con fork ed exec
- Richiami di C per la programmazione di rete - memoria, puntatori, struct ed endianness
- Sicurezza e protezione nei sistemi operativi