Allocazione dinamica della memoria

Transcript

Allocazione dinamica della memoria
Allocazione dinamica della memoria
Memoria stack e memoria dinamica (free store memory)
Abbiamo già parlato dell'area di memoria stack: é quella in cui viene allocato un pacchetto di dati non
appena l'esecuzione passa dal programma chiamante a una funzione. Abbiamo detto che questo pacchetto
(che contiene l'indirizzo di rientro nel programma chiamante, la lista degli argomenti passati alla
funzione e tutte le variabili automatiche definite nella funzione) viene "impilato" sopra il pacchetto
precedente (quello del programma chiamante) e poi automaticamente rimosso dalla memoria appena
l'esecuzione della funzione é terminata.
Sappiamo anche che, grazie a questo meccanismo, le funzioni possono essere chiamate ricorsivamente e
inoltre si possono gestire funzioni con numero variabile di argomenti. Le variabili automatiche definite
nella funzione hanno lifetime limitato all'esecuzione della funzione stessa proprio perché, quando la
funzione termina, il corrispondente pacchetto allocato nell'area stack viene rimosso.
Un'altra area di memoria è quella in cui vengono allocate le variabili non locali e le variabili locali statiche.
A differenza dalla precedente, quest'area viene mantenuta in vita fino alla fine del programma, anche se ogni
variabile è visibile solo all'interno del proprio ambito.
Esiste una terza area di memoria che il programma può utilizzare. Questa area, detta free store memory (o
heap nel linguaggio C), è soggetta a regole di visibilità e tempo di vita diverse da quelle che governano le
due aree precedenti, e precisamente:
 l'area free store non é allocata automaticamente, ma può essere allocata o rimossa solo su esplicita
richiesta del programma (allocazione dinamica della memoria);
 l'area allocata non é identificata da un nome, ma é accessibile esclusivamente tramite un puntatore;
 il suo scope coincide con quello del puntatore che contiene il suo indirizzo;
 il suo lifetime coincide con l'intera durata del programma, a meno che non venga esplicitamente
deallocata; se il puntatore va out of scope, l'area non é più accessibile, ma continua a occupare
memoria inutilmente: si verifica l'errore di memory leak, opposto a quello di dangling references.
Operatore new
In C++, l'operatore new costruisce uno o più oggetti nell'area free store e ne restituisce l'indirizzo. In caso
di errore (memoria non disponibile) restituisce NULL.
Gli operandi di new (tutti alla sua destra) sono tre, di cui solo il primo é obbligatorio (le parentesi quadre
nere racchiudono gli operandi opzionali):
new tipo [[dimensione]] [(valore iniziale)]


tipo é il tipo (anche astratto) dell'oggetto (o degli oggetti) da creare;
dimensione é il numero degli oggetti, che vengono sistemati nella memoria free store
consecutivamente (come gli elementi di un array); se questo operando é omesso, viene
costruito un solo oggetto; se é presente, l'indirizzo restituito da new punta al primo oggetto;
 valore iniziale é il valore con cui l'area allocata viene inizializzata (deve essere dello
stesso tipo di tipo); se é omesso l'area non é inizializzata.
NOTA: si è potuto riscontrare che a volte i due operandi opzionali sono mutuamente incompatibili (alcuni
compilatori più antichi danno errore): in pratica (vedremo perchè parlando dei costruttori), se il tipo è
nativo inizializza comunque tutti i valori con zero, se il tipo è astratto funziona bene (a certe condizioni).
Ovviamente l'operatore new non può restituire un l-value; può essere invece un r-value sia nelle
inizializzazioni che nelle assegnazioni, e può far parte di operazioni di aritmetica fra puntatori .
Esempi:
inizializzazione:
int* punt = new int (7);
assegnazione con operazione aritmetica:
struct anagrafico { ....... } ;
anagrafico* p_anag ;
p_anag = new anagrafico [100] + 9 ;
nel primo esempio alloca un oggetto int (inizializzato con il valore 7) nell'area free store e usa il suo
indirizzo per inizializzare il puntatore punt; nel secondo esempio definisce la struttura anagrafico e
definisce un puntatore a tale struttura, a cui assegna l'indirizzo del decimo di cento oggetti di tipo
anagrafico, allocati nell'area free store.
Operatore delete
In C++, l'operatore binario delete (con un operando opzionale e l'altro obbligatorio) dealloca la memoria
dell'area free store puntata dall'operando (obbligatorio). Non restituisce alcun valore e quindi deve essere
usato da solo in un'istruzione (non essendo né un l-value né un r-value non può essere usato in
un'espressione con altre operazioni).
Es.:
allocazione:
deallocazione:
int* punt = new int ;
delete punt ;
Contrariamente all'apparenza l'operatore delete non cancella il puntatore né altera il suo contenuto: l'unico
effetto é di liberare la memoria puntata rendendola disponibile per ulteriori allocazioni (se l'operando non
punta a un'area free store alcuni compilatori generano un messaggio di errore (o di warning), altri no, ma in
ogni caso l'operatore delete non ha effetto).
Se l'operando punta a un'area in cui è stato allocato un array di oggetti, bisogna inserire dopo delete
l'operando opzionale, che consiste in una coppia di parentesi quadre (senza la dimensione dell'array, che il
C++ é in grado di riconoscere automaticamente).
Es.:
float* punt = new float [100] ;
delete [ ] punt ;
(alloca 100 oggetti float )
(libera tutta la memoria allocata)
L'operatore delete costituisce l'unico mezzo per deallocare memoria free store, che, altrimenti, sopravvive
fino alla fine del programma, anche quando non é più raggiungibile.
Es.: int* punt = new int ;
int a ;
punt = &a ;
(alloca un oggetto int nell'area heap e inizializza punt con il suo
indirizzo)
(definisce un oggetto int nell'area stack)
(assegna a punt un indirizzo dell'area stack; l'oggetto int dell'area
heap non é più raggiungibile)
Esempio: con uso puntatori ed operatori new e delete per allocare dinamicamente
#include <iostream>
#include <cstdlib>
using namespace std;
int main()
{
int *p;
p=new int;
// uso variabile puntatore
if(p==NULL)
cout <<"impossibile allocare\n";
*p =20;
cout <<*p<<"\n";
// stampa 20
if (p)
delete p;
// allocazione dinamica di array
int dim = 0;
int*punt;
cout<<"Digita numero di elementi " ;
cin >> dim;
punt = new int[dim];
if(!punt) {
// ……………… gestione errore
//…………………………… uso
if (punt)
delete (punt);
}
// allocazione dinamica di array bidimensionale
int num_r=0;
int num_col=0;
int**pu;
// doppio puntatore a int
cout <<"righe ";
cin >>num_r;
cout <<"colonne ";
cin >>num_col;
pu=new int*[num_r];
if(pu==NULL) {
// …………… gestione errore
}
for (int i=0;i<num_r; i++) {
pu[i]=new int [num_col];
if(pu==NULL)
{ // …………………. gestione errore
}
}
// …………………………………… uso
for (int i =0; i<num_r;i++) {
if(!pu[i])
delete pu;
}
if(!pu)
delete pu;
system("Pause");
return (0);
}