Gestione della memoria dinamica

Quando in un programma si vogliono creare dinamicamente degli oggetti che rimangono in vita anche dopo il termine della funzione/procedura in cui sono creati, tali oggettti devono essere memorizzati nell’area di memoria chiamata free store o heap, che viene appunto gestita dinamicamente dal programma stesso.

Per motivi di semplicità ed efficienza, il linguaggio C non fornisce strumenti automatici di allocazione e rilascio della memoria dinamica (come ad esempio la garbage collection di Java): spetta al programmatore richiedere i blocchi di memoria che servono e poi rilasciarli quando non sono più necessari. E’ allora fondamentale fare molta attenzione a gestire correttamente la memoria dinamica, poichè le conseguenze di eventuali errori possono essere disastrose.

L’unico strumento che il C fornisce è un gestore della memoria (dinamica), al quale è appunto possibile richiedere e poi restituire blocchi di memoria per mezzo di apposite funzioni di libreria, tra cui le principali sono malloc, calloc e free. I prototipi di queste funzioni sono contenuti nel file di intestazione stdlib.h, che è dunque necessario includere per usarle.

malloc

La funzione malloc (”Memory ALLOCation”) ha il seguente prototipo:

void *malloc(size_t size);

Essa viene utilizzata per allocare nello heap un blocco di memoria costituito dal numero di byte specificati come parametro (il tipo di tale parametro, size_t, è un tipo intero senza segno che può assumere un intervallo di valori adeguato per rappresentare le possibili dimensioni degli oggetti in memoria).

maloc restituisce un valore di tipo puntatore a void, void*, che rappresenta un indirizzo privo di tipo, perchè la memoria allocata può essere utilizzata per memorizzare qualunque tipo di oggetto. Il valore restituito è:

Il valore nullo, corrispondente all’intero 0 è tipicamente rappresentato dalla macro NULL, è il valore convenzionalmente usato per indicare un puntatore che non punta a niente, e quindi non può essere dereferenziato (di solito, dereferenziare un puntatore nullo genera un errore in esecuzione).

Se una chiamata a malloc ha successo, è garantito che tutti i byte del blocco di memoria allocato siano utilizzabili e liberi (non usati per rappresentare altri oggetti già esistenti), ma non c’è alcuna garanzia sullo stato delle celle di memoria, che potrebbero contenere valori qualsiasi (ad esempio, se la memoria fosse stata precedentemente in uso, conterrebbe ancora i valori che vi erano stati scritti, poichè non viene azzerata quando la si rilascia e poi la si rialloca).

Esempi

Se si vuole allocare nello heap lo spazio necessario a contenere un valore intero, si usa la chiamata

malloc(sizeof(int));

per richiedere un blocco di memoria della dimensione di una variabile intera, e poi si assegna a un puntatore il risultato di tale chiamata, convertendolo (esplicitamente o implicitamente) da void* a int*:

int *pt = (int*)malloc(sizeof(int));

A questo punto, si può usare pt per accedere alla memoria allocata, esattamente come se fosse un puntatore a una “normale” variabile: