malloc

Da Wikipedia, l'enciclopedia libera.

In informatica, malloc (derivato dai termini memory allocation) è una funzione della libreria standard dei linguaggi di programmazione C e C++ per l'allocazione dinamica della memoria.

Concetti[modifica | modifica wikitesto]

Nel linguaggio di programmazione C la memoria può essere gestita staticamente, automaticamente o dinamicamente. Nella gestione statica le variabili sono allocate in una posizione fissa che persiste fino alla fine del programma; in quella automatica sono allocate nello stack e vengono inserite e rimosse in funzione delle chiamate e dei ritorni di funzione. Nell'allocazione statica la dimensione è richiesta durante il periodo di compilazione come costante. Se la dimensione non è nota durante il periodo di esecuzione (per esempio, se i dati devono essere inseriti via via da tastiera), questo metodo diventa inadeguato.

La durata dell'allocazione della memoria è un'altra fonte di preoccupazione. Né l'allocazione statica né quella automatica sono sufficienti per tutte le situazioni. In quella automatica i dati possono non persistere per più chiamate di funzione, mentre quella statica persiste per tutta la durata del programma sia nel caso in cui sia necessaria, sia in quello in cui non sia più utile. In molte situazioni il programmatore necessita di una maggiore flessibilità nella gestione del ciclo di vita della memoria allocata.

Queste limitazioni sono evitabili utilizzando l'allocazione dinamica della memoria, la quale è più esplicita (ma più flessibile), ed è gestita, in genere, mediante l'allocazione nell'heap. Il programma accede a questo blocco di memoria tramite un puntatore che la malloc restituisce. Quando non è più necessaria, la memoria può essere deallocata tramite la funzione free() in modo che possa essere utilizzata per altri scopi.

Alcune piattaforme forniscono delle chiamate alla libreria che permettono l'allocazione dinamica in run-time dallo stack piuttosto che dall'heap (per esempio glibc's alloca()[1], Microsoft Windows CRTL's malloca()[2]). Questa memoria viene liberata al termine della chiamata. La loro necessità è stata attenuata dai cambiamenti apportati nella C99, che ha aggiunto il supporto per gli array a lunghezza variabile nel blocco di scope aventi dimensioni determinate in run-time.

Allocazione dinamica della memoria in C[modifica | modifica wikitesto]

La funzione malloc è una di quelle appartenenti allo standard C per l'allocazione della memoria. Il suo prototipo è

void *malloc(size_t size);

Che alloca size byte di memoria. Se l'operazione ha successo, viene restituito un puntatore al blocco di memoria, mentre in caso contrario verrà restituito un puntatore null. In caso di successo, malloc restituisce un puntatore void (void *) che indica che si tratta di un puntatore ad una regione di dati di tipo sconosciuto. Non deve essere espresso in modo esplicito ad un puntatore di tipo specifico, poiché l'ANSI C definisce una conversione implicita tra il void ed altri tipi. Un cast esplicito è a volte presente poiché originariamente veniva restituito un char *, ma questo non è necessario nello standard C.[3][4] La sua omissione causa tuttavia una incompatibilità con il C++, che lo richiede.

La memoria allocata tramite malloc è persistente: ciò significa che continuerà ad esistere fino alla fine del programma o fino a quando non sarà esplicitamente deallocata dal programmatore (detto anche "liberata"). Questo risultato è ottenuto tramite la funzione free. Il suo prototipo è

void free(void* pointer);

Che rilascia il blocco di memoria indicato da pointer. pointer deve essere stato precedentemente allocato con malloc, calloc o realloc e deve essere passato una sola volta tramite free.

Esempi di utilizzo[modifica | modifica wikitesto]

Il metodo standard per creare un array di dieci interi:

int array[10];

Tuttavia, se si vuole assegnare un simile array in modo dinamico, si può utilizzare il seguente codice:

/* Alloca spazio per un array con 10 elementi di tipo int. */
int *ptr = malloc(10 * sizeof (int));
if (ptr == NULL) {
    /* La memoria non può essere allocata, il programma dovrebbe gestire l'errore in modo appropriato. */
} else {
    /* Allocazione avvenuta con successo.  Prosegui con il programma.  */
    free(ptr);  /* Abbiamo finito di lavorare con l'array, e liberiamo il puntatore associato. */ 
    ptr = NULL; /* Il puntatore non può essere più usato fino ad un riassegnamento effettuato con malloc. */
}

malloc restituisce un puntatore nullo per indicare che la memoria non è disponibile o che qualche altro errore gli ha impedito di allocarla.

Si può notare che a volte il codice restituito da malloc ha un "cast" ad uno specifico tipo, come in

int *ptr = (int*)malloc(10 * sizeof (int));

Ma questa è una cattiva pratica: è ridondante sotto lo standard C, come osservato in precedenza; e inoltre, il cast maschera l'eventuale mancata inclusione dell'header stdlib.h, che contiene il prototipo della malloc. In assenza del prototipo per malloc, il compilatore C assumerà che malloc restituisce un int, e manderà un avviso per indicare che la condizione dell'errore non sarà mascherata dal cast (avvisa quindi di non preoccuparsi perché sa come gestire la situazione)

Un utile idioma con la malloc è illustrato in questo esempio:

int *ptr = malloc(10 * sizeof (*ptr));

Ciò significa che, invece di scrivere un tipo hard-wired all'interno dell'argomento della malloc, usa un operatore sizeof sul contenuto del puntatore per l'assegnazione. Questo fa in modo che in caso di cambiamenti apportati durante una revisione del codice, i tipi a destra e a sinistra saranno sempre sincronizzati.

Funzioni collegate[modifica | modifica wikitesto]

Calloc[modifica | modifica wikitesto]

malloc restituisce un blocco di memoria allocato dal programmatore per essere utilizzato, ma non è inizializzato. Questa operazione è spesso effettuata a mano, se necessario, tramite la funzione memset, o da una o più assegnazioni con dereferenziazione del puntatore. Un'alternativa è di usare la funzione calloc, che alloca la memoria e la inizializza. Il prototipo è

void *calloc(size_t nelements, size_t elementSize);

che alloca un'area di memoria, la inizializza a 0, di dimensioni nelements × elementSize.

realloc[modifica | modifica wikitesto]

Spesso è utile che un blocco di memoria possa crescere o ridursi. Questo può essere fatto utilizzando realloc che restituisce un puntatore ad una zona di memoria di una specifica dimensione, che contiene gli stessi dati della vecchia regione indirizzata da pointer (troncata alla fine nel caso la nuova dimensione sia minore di quella precedente). Se realloc non è in grado di ridimensionare l'array sul posto, allocherà una nuova area delle nuove dimensioni, copierà i dati richiesti, e libererà il vecchio puntatore. Se l'allocazione fallisce, realloc mantiene il puntatore originale inalterato e restituisce un puntatore vuoto. La nuova area di memoria non è inizializzata (il suo contenuto non è prevedibile). Il prototipo della funzione è

void *realloc(void *pointer, size_t size);

la funzione realloc si comporta come malloc se il primo argomento è NULL:

void *p = malloc(42);
void *p = realloc(NULL, 42); /* equivalent */

Nello standard C89, realloc con dimensione 0 era uguale ad una chiamata a free(). Nello standard C99, non è stata mantenuta questa possibilità; ora, il blocco viene riallocato con una dimensione di zero byte ed è restituito un puntatore diverso da null (che non può essere direttamente dereferenziato, dato che non punta a nessuna area di memoria allocata, ma potrà essere usata per una futura chiamata a realloc o free).

Si deve sempre utilizzare una variabile temporanea. Ad esempio:

void *p = malloc(orig_size);
/* e dopo... */
void *tmp = realloc(p, big_size); 
if (tmp != NULL) {
   p = tmp; /* OK, nuovo assegnamento, più grande di p */
} else {
   /* gestire il problema in qualche modo */
}

Se invece uno ha

void *p = malloc(orig_size);
/* e dopo... */
p = realloc(p, big_size);

nel caso in cui non sia possibile ottenere big_size byte di memoria, p avrà un valore null e nessun puntatore all'area di memoria precedentemente allocata per p, creando un memory leak.

Errori comuni[modifica | modifica wikitesto]

L'uso improprio della malloc e delle relative funzioni può essere frequentemente fonte di problemi.

Allocazione fallita[modifica | modifica wikitesto]

malloc non garantisce il successo dell'operazione, e se non c'è memoria disponibile, o se il programma ha superato il limite di memoria che può referenziare, malloc restituirà un puntatore null. Molti programmi non controllano questa eventualità. Se il programma tenterà di utilizzare il puntatore null restituito dalla malloc come se fosse un normale puntatore ad un'area di memoria allocata, il programma andrà in crash.

Memory leak[modifica | modifica wikitesto]

Quando chiami una malloc, una calloc o una realloc con successo, il valore restituito dalla chiamata può eventualmente essere fatto passare per la funzione free. Questo rilascerà la memoria allocata, per permetterle di essere riutilizzata per altri scopi. Se questo non avviene, la memoria allocata non verrà liberata fino alla fine del processo; in altre parole, si formerà un memory leak. Tipicamente, un memory leak è causato dalla perdita di un puntatore, per esempio non utilizzando un puntatore temporaneo durante una chiamata alla realloc, che può portare la sovrascrittura del puntatore originale con un puntatore null, per esempio:

void *ptr;
size_t size = BUFSIZ;
 
ptr = malloc(size);
 
/* qui avvengono ulteriori operazioni... */
 
/* ora la dimensione del buffer deve essere raddoppiata */
if (size > SIZE_MAX / 2) {
  /* errore di overflow */
  /* ... */
  return (1);
}
size *= 2;
ptr = realloc(ptr, size);
if (ptr == NULL) {
  /* la realloc fallisce (e restituisce un puntatore null), ma il puntatore originale ptr è andato perduto 
     quindi la memoria non può essere liberata e avviene una perdita di memoria */
  /* ... */
  return 1;
}
/* ... */


Utilizzo dopo free()[modifica | modifica wikitesto]

Dopo che un puntatore è passato per una funzione free, il puntatore referenzia una regione di memoria, con indefinito contenuto, che non può essere utilizzata. Non si può accedere al contenuto del puntatore. Per esempio:

int *ptr = (int*)malloc(sizeof (int));
free(ptr);
*ptr = 0; /* comportamento indefinito */

Il codice avrà un comportamento i cui effetti possono variare. Anche il tentativo di stampare la variabile printf ha un effetto indefinito (assumendo che malloc non restituisca un puntatore null); per esempio:

printf("%p", (void *) ptr); /* comportamento indefinito */

Comunemente, il sistema può riutilizzare la memoria liberata per altri scopi. Pertanto, la scrittura in memoria verso un puntatore ad una regione deallocata, può avere come risultato una sovrascrittura di altri dati del programma stesso. A seconda del tipo di dati che vengono sovrascritti, questo può causare la corruzione dei dati stessi e un successivo crash del programma. Un cattivo esempio del tentativo di risolvere questo problema può essere il doppio passaggio tramite free, conosciuto come doppia liberazione. Per evitare questo, alcuni programmatori settano a NULL il puntatore dopo il passaggio per free:

free(ptr);
ptr = NULL; /*è sicuro (butta via la locazione del puntatore).*/

Questa funzione è sicura e non provoca anomalie,[5] tuttavia, anche questo accorgimento non riesce a proteggere da altri usi impropri dello stesso puntatore doppiamente liberato.

La prassi migliore è quella di passare fuori dallo scope (o visibilità) immediatamente dopo aver liberato la memoria.

Liberare memoria non allocata[modifica | modifica wikitesto]

Un altro problema può sopraggiungere passando un puntatore a free quando però non è stato precedentemente allocato con malloc, realloc o calloc. Il comportamento anomalo può essere causato quando un puntatore ad una stringa o ad un array, dichiarato in modo statico, è passato con la funzione free, per esempio:

char *msg = "Messaggio predefinito";
int tbl[100];

Il passaggio di entrambe i puntatori a free restituirà comunque un comportamento indefinito.

Limite spazio di allocazione[modifica | modifica wikitesto]

Il blocco di memoria più grande possibile che malloc può allocare dipende dal sistema sottostante, in particolare dalla memoria fisica e dall'implementazione nel sistema operativo. Teoricamente, il più grande numero dovrebbe essere il valore massimo che può essere contenuto in un tipo size_t, ossia una implementazione dipendente di intero senza segno che rappresenta la dimensione dello spazio di memoria. Il valore massimo è 28*sizeof(size_t) − 1, o la costante SIZE_MAX nello standard C99.

Voci correlate[modifica | modifica wikitesto]

Note[modifica | modifica wikitesto]

  1. ^ GNU libc manual on gnu.org accessed at 9 March 2007
  2. ^ malloca() page on MSDN Visual C++ Developer Center. Accessed on 12th March 2009
  3. ^ comp.lang.c FAQ list · Question 7.7b on C-FAQ accessed at 9 March 2007
  4. ^ FAQ > Explanations of... > Casting malloc on Cprogramming.com accessed at 9 March 2007
  5. ^ The Open Group Base Specifications Issue 6 on The Open Group accessed at 9 March 2007

Collegamenti esterni[modifica | modifica wikitesto]

informatica Portale Informatica: accedi alle voci di Wikipedia che trattano di informatica