Salta al contenuto
Note per Studenti Gestione della memoria in C

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 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

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:

malloc, calloc, realloc, free

Funzioni di <stdlib.h>:

c
#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 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

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 di v.
  • Ogni malloc riuscita deve avere esattamente un free corrispondente.

Array che cresce (come list.append)

c
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 n inserimenti è O(n)O(n): è 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à 11 e raddoppiando, 88 inserimenti copiano in tutto 1+2+4=71+2+4=7 elementi.

Restituire memoria da una funzione

c
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 malloc riuscita richiede un solo free.

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 nn inserimenti è O(n)O(n) (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