Menu

calloc e realloc in C: memoria azzerata e array che crescono

calloc ti dà memoria azzerata e moltiplica la dimensione in modo sicuro; realloc ingrandisce un blocco che hai già riempito. Questa pagina copre entrambe, l'idioma del puntatore temporaneo che evita perdite quando realloc fallisce e quando calloc batte malloc più memset.

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

malloc risponde a una sola domanda: dammi questo numero di byte. Due compagne in <stdlib.h> rispondono alle domande che vengono dopo: dammi questo numero di byte, azzerati (calloc) e mi serve che il blocco che ho già sia più grande (realloc).

calloc: numero, dimensione e zeri

void *calloc(size_t count, size_t size);

Due argomenti invece di uno, e il risultato è riempito di byte a zero.

È il caso classico: un istogramma o un array di conteggi in cui ogni posizione deve partire da zero. Con malloc ti servirebbe un ciclo; calloc lo fa come parte dell'allocazione, spesso gratis perché il sistema operativo fornisce già pagine nuove azzerate.

L'argomento dell'overflow

La divisione in due argomenti non è estetica. Considera un conteggio letto da un file:

size_t n = huge_value_from_input;

int *a = malloc(n * sizeof *a);     /* il prodotto può ricominciare da capo */
int *b = calloc(n, sizeof *b);      /* obbligata a rilevare l'overflow */

Se n * sizeof *a supera quello che size_t può contenere, ricomincia da un numero piccolo, malloc ha successo con un blocco minuscolo e ogni scrittura successiva va ben oltre la fine. Lo standard invece obbliga calloc a fallire e restituire NULL. Quando una dimensione arriva dall'esterno del programma, questo controllo vale la pena.

Cosa significa davvero "azzerata"

calloc scrive byte a zero. Per interi e caratteri questo è il valore zero, cioè quello che vuoi. Per puntatori e valori in virgola mobile, tutti i bit a zero corrispondono a un puntatore nullo e a 0.0 su ogni piattaforma diffusa, ma lo standard C non lo promette. Il codice che deve essere rigorosamente portabile li assegna in modo esplicito.

calloc contro malloc + memset

int *a = calloc(n, sizeof *a);

int *b = malloc(n * sizeof *b);
if (b != NULL) {
    memset(b, 0, n * sizeof *b);
}

Producono lo stesso risultato. Preferisci calloc: è una riga, fa il controllo di overflow e per blocchi grandi può evitare del tutto di toccare la memoria. Preferisci malloc semplice quando stai per sovrascrivere comunque ogni byte: azzerare un megabyte che riempirai subito è puro spreco.

realloc: cambiare la dimensione

void *realloc(void *p, size_t newsize);

realloc restituisce un blocco di newsize byte il cui contenuto coincide con quello del vecchio blocco fino alla minore delle due dimensioni. Può estendere il blocco dove si trova, oppure allocarne uno nuovo, copiare e liberare il vecchio. Non puoi sapere quale delle due, quindi l'unico puntatore che puoi usare dopo è quello restituito.

L'idioma del puntatore tmp

Le tre righe attorno a tmp sono l'intero motivo della reputazione di questa funzione. La scorciatoia allettante è sbagliata:

/* BUG: perde il blocco originale quando realloc fallisce */
p = realloc(p, newsize);
if (p == NULL) {
    return;
}

Quando realloc fallisce restituisce NULL e lascia il blocco originale allocato e invariato. Assegnare il risultato direttamente a p sovrascrive l'unico puntatore a quel blocco, che quindi non potrà mai essere liberato: un memory leak, e proprio nel momento in cui la memoria scarseggia già. Assegna a tmp, controlla tmp, poi conferma:

void *tmp = realloc(p, newsize);
if (tmp == NULL) {
    /* p è ancora valido; gestisci il fallimento come serve al chiamante */
    return 0;
}
p = tmp;

Altri due comportamenti da conoscere: realloc(NULL, n) si comporta esattamente come malloc(n), il che permette a una funzione di crescita di gestire la prima chiamata senza un caso speciale. E ingrandire lascia i nuovi byte non inizializzati: viene preservato solo il contenuto vecchio.

Far crescere un array: lo schema standard

Ecco lo schema che usa quasi ogni contenitore C: una lunghezza, una capacità e il raddoppio quando si incontrano.

Due scelte di progettazione reggono tutto.

Raddoppiare, non aggiungere uno. Crescere di una quantità fissa fa sì che ogni inserimento copi l'intero array, quindi riempire n elementi costa circa n² copie di byte. Raddoppiare rende le copie abbastanza rare che ogni inserimento costa in media un tempo costante. L'avvio cap == 0 ? 4 : cap * 2 gestisce il primo inserimento senza un ramo separato, perché realloc(NULL, n) è semplicemente malloc.

La struct possiede un'unica allocazione. v.data viene liberato esattamente una volta, da chi possiede la struct. Mettere per iscritto questa regola di possesso, in un commento o accoppiando ogni vec_init con un vec_free, è ciò che impedisce a un contenitore che cresce di perdere memoria mentre passa da una funzione all'altra.

Ridurre

realloc con una dimensione minore accorcia un blocco, cosa utile dopo aver letto una quantità di dati sconosciuta in un buffer sovradimensionato:

Una riduzione fallita non è un errore per cui valga la pena interrompere: il vecchio blocco più grande è ancora valido e ancora corretto. Questo è l'unico caso in cui ignorare il fallimento è la scelta giusta, ma nota che passa comunque da tmp, perché la regola di non sovrascrivere il puntatore attivo non cambia.

Quale usare

Ti serveUsa
Dei byte, contenuto irrilevantemalloc(n * sizeof *p)
Un numero di elementi, azzeraticalloc(n, sizeof *p)
Un numero che arriva da input non fidatocalloc, per il controllo di overflow
Il blocco che hai, più grande o più piccolorealloc tramite un puntatore tmp
La prima allocazione in un ciclo di crescitarealloc(NULL, n), nessun caso speciale

Tutte e quattro si rilasciano con la stessa free, esattamente una volta.

Errori comuni

  • p = realloc(p, n): perde memoria in caso di fallimento. Passa sempre da tmp.
  • Tenere un vecchio puntatore dentro il blocco. Dopo realloc i dati potrebbero essersi spostati, quindi ogni puntatore interno salvato o indirizzo derivato da un indice è obsoleto. Gli indici sopravvivono a uno spostamento, i puntatori no.
  • Dare per scontato che i nuovi byte siano a zero. Solo calloc azzera. realloc lascia non inizializzata la zona aggiunta.
  • calloc(n * size, 1): butta via il controllo di overflow per cui esiste la forma a due argomenti.
  • Liberare il vecchio puntatore dopo una realloc riuscita. realloc l'ha già rilasciato; una seconda free è un double free.

Domande frequenti

Qual è la differenza tra calloc e malloc in C?

Due differenze. calloc(n, size) riceve separatamente il numero di elementi e la dimensione di ciascuno e controlla che la loro moltiplicazione non vada in overflow, mentre malloc(n * size) può sforare in silenzio. E calloc riempie il blocco di zeri, mentre malloc lo lascia con quello che c'era prima.

Come funziona realloc in C?

realloc(p, newsize) restituisce un blocco della nuova dimensione con il contenuto esistente preservato fino alla minore delle due dimensioni. Può ingrandire il blocco sul posto oppure spostarlo, quindi il puntatore restituito è l'unico che puoi continuare a usare: quello vecchio potrebbe essere già stato liberato.

Perché assegnare il risultato di realloc a un puntatore temporaneo?

Perché p = realloc(p, n) sovrascrive p con NULL quando la chiamata fallisce, e il blocco originale resta allocato senza che nulla punti a esso: un memory leak. Assegna prima a un tmp, controlla che non sia NULL e solo dopo scrivi p = tmp.

Quando usare calloc invece di malloc?

Quando ti serve davvero memoria azzerata (un array di contatori, una struct i cui campi devono partire vuoti, un buffer che riempirai solo in parte) oppure quando la dimensione è un numero di elementi per una dimensione dell'elemento che potrebbe essere grande. Se sovrascriverai comunque ogni byte, malloc evita il lavoro di azzeramento.

Illustrazione dei linguaggi di programmazione di Coddy

Impara a programmare con Coddy

INIZIA