Utilizziamo cookie tecnici e di profilazione (anche di terze parti) per migliorare la tua esperienza su questo sito. Continuando la navigazione accetti l'utilizzo dei cookie; in alternativa, leggi l'informativa e scopri come disabilitarli.

Le strutture dati del linguaggio C sono normalmente di dimensione fissa. Finora bbiamo dichiarato variabili di molti tipi (array, stringhe strutture ecc.) però statiche, cioe' la cui dimensione e' fissata a tempo di compilazione: questo puo' essere un problema, perchè siamo costretti a fissare le loro dimensioni al momento della scrittura del programma. Quindi non possiamo modificare le dimensioni, senza dover modificare il codice e ricompilare il programma.

LE FUNZIONI PER ALLOCARE MEMORIA

Il C fornisce quattro funzioni per allocare dinamicamente la memoria:

  • Malloc: alloca un blocco di memoria senza inizializzarlo
  • Calloc: alloca un blocco di memoria e lo inizializza
  • Realloc: ridimensiona un blocco di memoria allocato in precedenza
  • Free: libera un blocco di memoria

Fra malloc e calloc ci sono due differeza: la prima è che la malloc non inizializza il blocco di memoria. Di conseguenza, effettuando meno operazioni, è più efficiente. La seconda e' che la malloc richiede un solo parametro, la dimensione del blocco di memoria da allocare, la calloc la calcola partendo da due parametri.

Descrizione dettagliata

  • #include <stdlib.h>
  • void *calloc(unsigned int number, unsigned int size)

    Alloca numeber elementi ognuno di size byte nello heap. La memoria viene inizializzata a 0.

    La funzione restituisce il puntatore alla zona di memoria allocata in caso di successo e NULL in caso di fallimento, nel qual caso errnoassumerà il valore ENOMEM.

  •  void *malloc(unsigned int size)

    Alloca size byte nello heap. La memoria non viene inizializzata.

    La funzione restituisce il puntatore alla zona di memoria allocata in caso di successo e NULL in caso di fallimento, nel qual caso errnoassumerà il valore ENOMEM.

  •  void *realloc(void *ptr, insigned int size)

    Cambia la dimensione del blocco allocato all'indirizzo ptr portandola a size.

    La funzione restituisce il puntatore alla zona di memoria allocata in caso di successo e NULL in caso di fallimento, nel qual caso errnoassumerà il valore ENOMEM.

  • void free(void *ptr)

    Disalloca lo spazio di memoria puntato da ptr.

    La funzione non ritorna nulla e non riporta errori.

Il puntatore ritornato dalle funzioni di allocazione è garantito essere sempre allineato correttamente per tutti i tipi di dati; ad esempio sulle macchine a 32 bit in genere è allineato a multipli di 4 byte e sulle macchine a 64 bit a multipli di 8 byte.

In genere si usano le funzioni malloc e calloc per allocare dinamicamente la quantità di memoria necessaria al programma indicata da size, e siccome i puntatori ritornati sono di tipo generico non è necessario effettuare un cast per assegnarli a puntatori al tipo di variabile per la quale si effettua l'allocazione.

E' SEMPRE POSSIBILE ALLOCARE MEMORIA?

Non sempre, infatti quando una funzione per l'allocazione della memoria viene invocata c'è sempre la possibilità (anche se remota per programmi di piccole dimensioni) che questa non sia in grado di allocare spazio a sufficienza. Se questo dovesse succedere, la funzione di allocazione invocata, restituisce un puntatore nullo che sicuramente avrete già visto e usato: NULL (definito in ben sei header differenti, ma basterà includere <stdio.h>). Dunque dopo aver salvato il valore restituito da una funzione in una variabile puntatore, è buona (se non OTTIMA) norma, controllarlo con un IF. Il C da molto spazio di scelta al programmatore dunque è nostra responsabilità controllare sempre il valore restituito dalle funzioni. L'effetto, ad esempio, di un tentato accesso alla memoria tramite puntatore nullo, non è definito. Il programma nel migliore dei casi andrà in crash, o si comporterà in modo indefinito.

ESEMPIO:

int *p;  //Dichiariamo una variabile puntatore

p=malloc(1000);   //Allochiamo spazio tramite la funzione malloc()

if (p==NULL)    //Ecco il controllo sul valore restituito dalla malloc()

{

printf("Impossibile allocare memoria!\n");

exit(EXIT_FAILURE);

}

Se si verifica la condizione espressa nel predicato dell'if, allora la funzione exit esce subito dal programma. EXIT_FAILURE non è altro che una macro che ha valore 1.

 

ALLOCAZIONE DINAMICA DI STRINGHE

Le funzioni di memory allocation, sono molto utili quando si lavora con le stringhe. Sappiamo che, per le stringhe, in quanto vettori di caratteri, è difficile dichiarare a priori la lunghezza senza sprecare memoria.

Esempio:

char *p;

p=malloc(100);

oppure

p=(char*)malloc(100);

Spiegazione: abbiamo dichiarato una variabile puntatore a carattere e con la malloc le abbiamo assegnato 100 byte, quindi possiamo memorizzare stringhe lunghe fino a 99 caratteri (le stringhe hanno il terminatore \0).

La seconda opzione ha il casting char*: Non è obbligatorio, tuttavia molti programmatori sono soliti effettuare il casting del valore restituito per sicurezza.

 

ALLOCAZIONE DINAMICA DI VETTORI

A questo punto starete pensando, ma le stringhe non sono vettori?

Niente di più giusto, ma sappiamo che le stringhe hanno ogni elemento di un byte. I vettori invece possono avere elementi che occupano anche più di un byte! Infatti quando allocheremo memoria per un vettore di interi ad esempio, per conferire anche maggiore portabilità al programma (perché gli interi non occupano lo stesso spazio su tutti i computer), useremo l'operatore sizeof(int). ESEMPIO:

int *a;

a=malloc(n*sizeof(int));

LA FUNZIONE CALLOC

Abbiamo parlato fin'ora della malloc, ma talvolta la funzione calloc può essere un'utile alternativa.

Le differenze fra malloc e calloc sono riportate sopra.

ESEMPIO:

int *a;

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

Questa scrittura, alloca dello spazio per un vettore di n interi che vengono inizialmente, inizializzati tutti a zero. Anche se a livello di efficienza, la malloc è più veloce, la calloc può essere molto utile in certe situazioni!

LA FUNZIONE REALLOC

Dopo aver allocato memoria per un vettore, possiamo accorgerci che questa è troppo grande o troppo piccola. Ed ecco che ci viene incontro la realloc che ridimensiona il vettore in base alle nostre richieste. La sua sintassi e':

void *realloc(void *ptr, size n);

Quando viene chiamata, ptr deve puntare ad un blocco di memoria allocato precedentemente dalla funzioni malloc, calloc o realloc. Il parametro n rappresenterà la nuova dimensione del vettore che può essere benissimo più grande o più piccola di quella originaria.

Il C però definisce alcune regole (da sapere) sulla realloc:

  • Se espande un blocco di memoria, la realloc non inizializza i byte che vengono eventualmente aggiunti.
  • Se non è possibile aumentare il size del blocco, la realloc restituisce un puntatore nullo e i dati presenti nel vecchio blocco vengono lasciati inalterati.
  • Se passiamo alla realloc un puntatore NULL, si comporta come la malloc.
  • Se la realloc viene chiamata passando 0 come secondo argomento, libera il blocco di memoria

Si deve sempre avere ben presente il fatto che il blocco di memoria restituito da realloc può non essere un'estensione di quello che gli si è passato in ingresso; per questo si dovrà sempre eseguire la riassegnazione di ptr al valore di ritorno della funzione, e reinizializzare o provvedere ad un adeguato aggiornamento di tutti gli altri puntatori all'interno del blocco di dati ridimensionato.

DEALLOCARE MEMORIA

Le funzioni di allocazione dinamica ottengono dei blocchi di memoria da un'area che si chiama HEAP. Chiamarle troppo spesso potrebbe esaurire lo heap, e di conseguenza le funzioni di allocazione, potrebbero restituire dei puntatori nulli per comunicarci che l'allocazione non è andata a buon fine.

Un blocco di memoria che non sia più accessibile da parte di un programma, viene detto GARBAGE, (spazzatura). Il C purtroppo lascia al programmatore la responsabilità di "raccogliere ed eliminare" la spazzatura. Mentre altri linguaggi come Basic o Java, hanno lo strumento di Garbage Collector che si occupa per conto nostro di liberare aree di memoria inutilizzate.

In C, dimenticarsi di liberare aree di memoria allocate precedentemente può portare ad uno dei più gravi errori di programmazione, chiamato Memory Leak.

Il C dunque fornisce una funzione chiamata free( ), che libera il puntatore ad un'area di memoria allocata precedentemente.

ESEMPIO:

char *b;

b=malloc(n+1);

...

free(b);

In questo modo abbiamo allocato spazio per una stringa, scriviamo il nostro bel codice, e alla fine ci ricordiamo di liberare quell'area di memoria allocata in precedenza.

ATTENZIONE: L'argomento della funzione free deve essere necessariamente un puntatore che è stato precedentemente restituito da un a funzione di allocazione dinamica. Passare a questa funzione un puntatore a qualsiasi altro oggetto, provoca un comportamento indefinito!

Un errore abbastanza frequente (specie se si ha a che fare con vettori di puntatori) è quello di chiamare free più di una volta sullo stesso puntatore; per evitare questo problema una soluzione di ripiego è quella di assegnare sempre a NULL ogni puntatore liberato con free, dato che, quando il parametro è un puntatore nullo, free non esegue nessuna operazione.