La gestione della memoria DOTT. ING. LEONARDO RIGUTINI DIPARTIMENTO INGEGNERIA DELL INFORMAZIONE UNIVERSITÀ DI SIENA VIA ROMA 56 53100 SIENA UFF. 0577234850-7102 RIGUTINI@DII.UNISI.IT HTTP://WWW.DII.UNISI.IT/~RIGUTINI/
Le gestione della memoria Il C è, per natura, un linguaggio molto flessibile, e la sua gestione della memoria contribuisce a renderlo ancora più flessibile. A differenza di altri linguaggi (come il C++ o il Java), il C permette di assegnare la giusta quantità di memoria (solo e solamente quella necessaria) alle variabili del programma. Utilizzare queste caratteristiche del C permette di creare programmi altamente portabili, in quanto utilizzano di volta in volta i valori giusti per la piattaforma.
La gestione della memoria la memoria è divisa sostanzialmente in due parti: - una statica, che contiene tutto quello che sappiamo verrà allocato (come una variabile int, una struttura definita con struct ecc ) e che si chiama Stack, - una dinamica, cioè in cui la dimensione di memoria per rappresentare qualche elemento del programma può cambiare durante l'esecuzione del programma, che si chiama Heap.
La gestione della memoria: lo Heap Lo Heap è un lotto di memoria assegnato ad un processo specifico, per immagazzinare strutture dati, la cui esistenza o dimensione non possa essere determinata prima che il programma sia eseguito. Quando sono richiesti blocchi di memoria nello heap è necessario utilizzare una apposita funzione per allocare memoria. Una volta finito di utilizzare lo spazio ottenuto è necessario liberare la memoria allocata con un altra funzione (memoria dinamica). Se necessario è possibile chiedere blocchi aggiuntivi nello heap utilizzando un altra apposita funzione. La memoria nello Heap si può accedere solamente attraverso un puntatore o una catena di puntatori allocati nello Stack.
La gestione della memoria: lo Heap Lo heap è gestito dal S.O. in paragrafi minimi (di 16, 32 o 64 byte): - la richiesta di un certo quantitativo di memoria viene arrotondato per eccesso al multiplo successivo del paragrafo minimo; - inoltre, viene generalmente aggiunto un paragrafo per informazioni accessorie.
La gestione della memoria: lo Heap L uso continuo di allocazione e rilascio di memoria heap causa un frazionamento dello spazio disponibile con conseguente peggioramento delle prestazioni: solo alcuni S.O. possono deframmentare lo spazio, ridisponendo tutti i blocchi. Il programmatore deve attentamente valutare costi e benefici della gestione Heap, sostituendola quando possibile con un assegnazione iniziale di tutto lo spazio necessario, o con la creazione dinamica di spazio sullo Stack.
La gestione della memoria Le funzioni utilizzate per gestire la memoria dinamica sono principalmente quattro: - malloc() e calloc() adibite all'allocazione della memoria, - free() che, come si intuisce, serve per liberare la memoria allocata, - realloc() la cui funzione è quella di permettere la modifica di uno spazio di memoria precedentemente allocato. Un comando particolarmente utile risulta essere sizeof, che restituisce la dimensione del tipo di dato da allocare. Queste funzioni sono disponibili nella standard library: in particolare è necessario includere il file malloc.h
La gestione della memoria E possibile chiedere la dimensione di un tipo di dato o di una variabile in memoria attraverso l istruzione sizeof size_t sizeof(oggetto) size_t sizeof(tipo) typedef restituisce un valore di tipo size_t: - esso è un typedef di int, ovvero un intero E necessario includere il file stddef.h #include <stddef.h>
malloc() void * malloc ( size_t S); La funzione malloc alloca un blocco di memoria della dimensione di S byte, e restituisce : - un puntatore void con l indirizzo dello spazio allocato correttamente; - NULL se la memoria disponibile è insufficiente. Per ottenere un puntatore a tipi diversi da void, occorre effettuare un operazione di cast sul valore restituito: - lo spazio di memoria assegnato può contenere dati di qualsiasi tipo.
malloc() Se size è 0, malloc alloca un elemento di lunghezza NULLA nello heap e ne restituisce l indirizzo. Occorre sempre controllare il valore restituito da malloc, anche se la memoria richiesta è piccola. malloc alloca memoria almeno di Size byte, ma il blocco occupato può essere più grande a causa dello spazio richiesto per l allineamento dei dati e per la manutenzione dei blocchi.
calloc() void * calloc ( size_t N, size_t S); La funzione calloc alloca spazio di memoria nello heap per un array di N elementi, ciascuno di dimensioni S byte. Ogni elemento è inizializzato a zero. Per il resto calloc esegue le stesse operazioni della funzione malloc.
Deallocazione della memoria Una volta allocato un oggetto nello Heap, esso non ne viene più rimosso, anche se la procedura che lo ha allocato termina. Poiché ogni area allocata nello Heap è raggiungibile solo mediante puntatore, durante l esecuzione di un programma ogni area allocata dinamicamente deve essere accessibile attraverso un puntatore da una variabile allocata nello Stack.
Deallocazione della memoria Se ciò non avviene più poiché la variabile puntatore viene persa quell area dello Heap diventa inaccessibile dal programma pur rimanendo però allocata (ovvero inutilizzabile per le altre successive allocazioni): - Se tale caso si ripete più volte, si può avere out-of-memory E buona norma di programmazione non lasciare che ciò avvenga deallocando le aree di memoria prima che si perdano i puntatori e diventino inaccessibili.
Deallocazione della memoria Certi compilatori o linguaggi di programmazione (es. Java) sono in grado di riconoscere le aree non più raggiungibili e di rilasciarle senza che ciò sia fatto esplicitamente dal programmatore. - questa operazione si chiama Garbage Collection La garbage collection però richiede operazioni aggiuntive per individuare le aree idonee ad essere liberate e quindi una perdita di prestazioni.
free() void free ( void * BlockAddress ); La funzione free rilascia un blocco di memoria nello heap, di indirizzo BlockAddress, che sia stato precedentemente allocato con chiamate a malloc, calloc o realloc. Se BlockAddress è NULL, il puntatore è ignorato e free esegue un immediato ritorno dalla funzione. Tentativi di liberare memoria non allocata da malloc, calloc o realloc, o già liberata, possono influire sulle successive allocazioni e causare errori imprevedibili. La funzione free non restituisce alcun valore.
malloc() e free() #include <stdio.h> #include <stdlib.h> #include <malloc.h> int main() { int numero, *array, i; char buffer[15]; int allocati; numero = 100; printf("numero di elementi dell'array: %d", numero); array = (int *)malloc(sizeof(int) * numero); if (array == NULL) { } printf("memoria esauritan"); exit(1);
malloc() e free() } allocati = sizeof(int) * numero; for (i=0; i<numero; i++) { } array[i] = i; printf( \nvalori degli elementi\n"); for (i=0; i<numero; i++) { if (i%10 == 9 ) printf("%6d%c", array[i],'n ); } else printf("%6d%c", array[i], ' '); printf( \n\nnumero elementi %d\n", numero); printf("dimensione elemento %d\n", sizeof(int)); printf("bytes allocati %d\n", allocati); free(array); printf( \nmemoria Liberata\n"); return 0;
malloc() e free() In questo programma possiamo notare l'uso della funzione malloc() che ritorna un puntatore a int, corrispondente al punto di inizio, in memoria, della porzione riservata della dimensione "intera" passata come argomento Se la memoria richiesta non può essere allocata, ritorna un puntatore nullo. Nel caso citato si può notare che è stato usata la funzione sizeof per specificare il numero esatto di byte, mentre è stata usato il cast per convertire il tipo di dato "puntatore a void" a "puntatore ad int", questo per garantire che i puntatori aritmetici vengano rappresentati correttamente.
realloc() void * realloc ( void * BlockAddress, size_t NewSize ); La funzione realloc cambia la dimensione di un blocco di memoria heap precedentemente allocato con malloc, calloc o realloc il cui indirizzo iniziale è BlockAddress e lo porta a NewSize byte Restituisce un puntatore void al blocco di memoria riallocato ed eventualmente spostato: il nuovo indirizzo restituito, dunque, potrebbe essere differente da BlockAddress.
realloc() Il contenuto del blocco non è modificato per un numero di byte pari alla minore tra vecchia e nuova dimensione, anche se il nuovo blocco potrebbe risiedere in una diversa zona di memoria. Se BlockAddress è NULL e NewSize è diverso da zero, realloc è equivalente ad una malloc che alloca da zero una quantità di memoria pari a NewSize. Se non c è abbastanza memoria disponibile per espandere il blocco alla dimensione richiesta, realloc restituisce NULL e il blocco non è modificato.
realloc() Se BlockAddress è un puntatore valido e NewSize è zero, realloc libera il blocco e restituisce NULL: - In paratica si comporta come una chiamata a free(blockaddress) Per ottenere un puntatore a tipi diversi da void, occorre effettuare una operazione di cast sul valore restituito. Lo spazio di memoria assegnato può contenere dati di qualsiasi tipo.
realloc() Esempio di uso della funzione realloc() per definire un array flessibile, ovvero un array cui viene riservata della memoria suddivisa in blocchi di dimensione arbitraria. Una volta "saturato" il primo blocco, utilizziamo realloc() per allocare il blocco successivo. La suddivisione in blocchi avviene durante la fase di lettura, che essendo, appunto, dinamica, permette di minimizzare le chiamate a realloc() e di avere un dimensionamento abbastanza preciso.
realloc() #include <stdio.h> #include <stdlib.h> #include <malloc.h> int main() { char buffer[20]; int i=0, n=0, x, *array, nb; int allocati; /* byte allocati */ int dimbloc; /* byte in un blocco */ int dimint; /* byte in un intero */ int usati; /* byte contenenti interi */ nb = 1; printf("elementi in un blocco: %d\n", nb); dimint = sizeof(int); dimbloc = nb * dimint; usati = 0;
realloc() array = (int *)malloc(dimbloc); if (array == NULL) { printf("memoria insufficiente\n"); exit(1); } allocati = dimbloc; printf("allocati: %d bytes\n", allocati); printf("input di interi terminati da # :\n");
realloc() while(scanf("%d", & x)) { usati += dimint; if (usati>allocati) { allocati += dimbloc; array = (int *)realloc(array, allocati); if (array == NULL) { printf("memoria insufficienten"); exit(1); } i++; } /* in questo modo vengono letti n interi */ array[n++] = x; }
realloc() } printf( \n"); printf("allocati: %d bytes\n", allocati); printf("dim. blocchi: %d bytes\n", dimbloc); printf("dim. intero: %d bytes\n", dimint); printf("usati: %d bytes\n", usati); printf("chiamate realloc: %d\n", i); printf("numeri: %d\n", n); printf( \necco i numeri\n"); for (i=0; i<n; i++) { if (i %10 == 9) printf("%5d%c", array[i], 'n ); else printf("%5d%c", array[i], ' '); } printf("n"); return 0;