Menu

malloc in C: allocazione dinamica della memoria, free e sizeof

Perché esiste l'heap, come malloc ti dà memoria la cui dimensione si decide a runtime, l'idioma p = malloc(n * sizeof *p), il controllo di NULL, liberare esattamente una volta e un array dinamico dall'inizio alla fine.

Questa pagina include editor eseguibili: modifica, esegui e vedi subito l'output.

Ogni array che hai scritto finora aveva la dimensione fissata in fase di compilazione: int scores[10]; riserva dieci posizioni sia che te ne servano tre sia che te ne servano trecento. Funziona finché la dimensione non è nota solo mentre il programma gira: quante righe ha un file, quanti record inserisce l'utente. La memoria dinamica risolve questo problema: chiedi i byte che ti servono, quando ti servono, e li restituisci quando hai finito.

Gli strumenti si trovano in <stdlib.h>:

#include <stdlib.h>

Perché esiste l'heap

Il C dà al tuo programma due regioni principali di memoria. Le variabili locali vivono nella memoria automatica (lo stack): il compilatore decide la loro dimensione e spariscono nel momento in cui la loro funzione termina. Le allocazioni dinamiche vivono nell'heap: decidi tu la dimensione a runtime e il blocco resta vivo finché non lo liberi esplicitamente.

Questa seconda proprietà è il vero motivo per usare malloc. Una funzione può allocare un blocco, restituire il puntatore, e la memoria è ancora valida nel chiamante: una cosa che un array locale non può mai fare. Le due regioni sono confrontate in dettaglio in stack e heap.

malloc: chiedere dei byte

malloc riceve un numero di byte e restituisce un void * che punta a quel numero di byte di memoria non inizializzata, oppure NULL se la richiesta fallisce.

Quattro cose in questo breve programma sono l'intera disciplina:

  1. L'espressione della dimensione è n * sizeof *scores. Leggila come "n volte ciò a cui punta scores".
  2. Il risultato viene confrontato con NULL prima di qualsiasi uso.
  3. Il blocco si usa esattamente come un array: scores[i] funziona perché l'indicizzazione è aritmetica dei puntatori.
  4. Viene liberato una volta e poi il puntatore viene impostato a NULL.

Nota che non c'è alcun cast sul valore restituito. In C, void * si converte automaticamente in qualsiasi puntatore a oggetto, e scrivere (int *)malloc(...) aggiunge rumore nascondendo un eventuale include di <stdlib.h> mancante. (Il C++ richiede il cast; il C no.)

L'idioma di sizeof

Perché sizeof *scores invece di sizeof(int)? Perché non può diventare obsoleto.

int  *a = malloc(n * sizeof *a);     /* n int, qualunque sia la dimensione di int qui */
long *b = malloc(n * sizeof *b);     /* stessa forma della riga, dimensione giusta */

/* la forma fragile */
long *c = malloc(n * sizeof(int));   /* compila, alloca troppo poco */

L'ultima riga è un bug reale per cui nessun compilatore avvisa: c è un long *, l'allocazione è dimensionata per int e ogni scrittura oltre la prima metà esce dalla fine. Con sizeof *c la dimensione segue automaticamente la dichiarazione.

sizeof *p non dereferenzia p: sizeof viene valutato in fase di compilazione a partire dal solo tipo, quindi è sicuro anche quando p non è inizializzato o è NULL.

Memoria non inizializzata

malloc non pulisce quello che ti dà. I byte contengono quello che c'era prima.

Quando vuoi che il blocco venga azzerato per te, calloc lo fa in un solo passaggio: vedi calloc e realloc.

free: restituire la memoria

free(p) restituisce il blocco all'allocatore. Tre regole:

  • Passa esattamente il puntatore restituito da malloc, non uno che è stato fatto avanzare. free(p + 1) è comportamento indefinito.
  • Libera ogni blocco esattamente una volta. Due volte è un double free, che corrompe la contabilità interna dell'allocatore.
  • Dopo la liberazione il puntatore è obsoleto. Usarlo è un use-after-free.
int *p = malloc(sizeof *p);
free(p);
*p = 5;        /* use-after-free: comportamento indefinito */
free(p);       /* double free: comportamento indefinito */

Nessuna delle due righe è obbligata a far crashare subito il programma, ed è questo che le rende pericolose: il programma può girare per minuti e poi fallire in un punto che non c'entra nulla. La difesa economica è azzerare il puntatore:

free(p);
p = NULL;

free(NULL) è esplicitamente definito come un'operazione che non fa nulla, quindi una seconda free(p) dopo di esso è innocua, e *p diventa un crash immediato nel punto esatto dell'errore invece di una corruzione silenziosa.

Un array dinamico, dall'inizio alla fine

Ecco la forma di un'allocazione reale: leggi un conteggio a runtime, dimensiona il blocco in base a esso, usalo, liberalo.

Il cast a size_t sul conteggio conta quando le dimensioni diventano grandi: n * sizeof *values con un n di tipo int semplice può andare in overflow prima ancora che la moltiplicazione arrivi a malloc, producendo un blocco molto più piccolo del previsto. Moltiplicare in size_t lo evita.

Allocare dentro una funzione

La regola sulla durata dell'heap è ciò che rende lecito questo, e ciò che rende illecito restituire un array locale:

len + 1 lascia spazio per il terminatore nullo: la stessa regola di dimensionamento che vale ovunque per le stringhe. E nota il commento sopra la funzione: quando una funzione restituisce memoria allocata, chi deve liberarla fa parte del suo contratto. Metterlo per iscritto non è burocrazia: è l'unica cosa che impedisce un leak.

Struct nell'heap

Lo stesso idioma funziona per qualsiasi tipo:

Nota che il secondo ramo di errore libera p prima di uscire. Quando una funzione detiene più di un'allocazione, ogni percorso di errore deve rilasciare quelle già ottenute: è la fonte più comune dei leak descritti nei memory leak.

Checklist

  • Controlla sempre che malloc non abbia restituito NULL prima del primo uso.
  • Dimensiona con n * sizeof *p, moltiplicando in size_t.
  • In C niente cast sul valore restituito.
  • La memoria di malloc non è inizializzata; scrivi prima di leggere.
  • Libera esattamente una volta, con il puntatore originale, poi impostalo a NULL.
  • Ogni percorso di errore dopo un'allocazione deve liberare ciò che già detiene.

Domande frequenti

Cosa fa malloc in C?

malloc(n) chiede n byte di memoria all'heap e restituisce un puntatore all'inizio di quel blocco, oppure NULL se la richiesta non può essere soddisfatta. Il blocco vive finché non passi quello stesso puntatore a free: a differenza di una variabile locale, non viene rilasciato quando la funzione termina.

Come si usano malloc e free in C?

Alloca con int *p = malloc(n * sizeof *p);, controlla if (p == NULL) prima di toccarlo, usalo come un array, poi chiama free(p); esattamente una volta quando hai finito. Impostare p = NULL subito dopo trasforma un uso accidentale successivo in un crash pulito invece che in una corruzione silenziosa.

Perché malloc(n * sizeof *p) è meglio di malloc(n * sizeof(int))?

Perché sizeof *p segue automaticamente il tipo del puntatore. Se in seguito p diventa un long * o uno struct Point *, la dimensione dell'allocazione si aggiorna da sola; scrivere esplicitamente sizeof(int) lascia una dimensione ormai sbagliata che il compilatore non segnalerà.

Cosa succede se non liberi la memoria in C?

Il blocco resta allocato per tutta la vita del processo: un memory leak. Un programma breve se la cava perché il sistema operativo recupera tutto all'uscita, ma un programma che gira a lungo perde un po' di memoria a ogni passaggio e alla fine la esaurisce. Vedi i memory leak.

Illustrazione dei linguaggi di programmazione di Coddy

Impara a programmare con Coddy

INIZIA