next up previous contents index
Next: Gli operatori new e Up: Memoria dinamica. Strutture. Liste Previous: Memoria dinamica. Strutture. Liste   Indice   Indice analitico

Memoria dinamica

Prima di introdurre i nuovi operatori new e delete, vediamo brevemente come il C++ gestisce la memoria principale necessaria al funzionamento di un programma; cercheremo comunque di non addentrarci troppo nei meccanismi tecnici, i quali sono complessi ed esulano dagli scopi del nostro testo. Quando eseguiamo un programma, esso riserva immediatamente dello spazio nella memoria centrale in una zona detta stack, la quale è sufficiente a contenere tutte le variabili automatiche utilizzate dal programma; tutte le variabili che abbiamo sino ad ora creato erano di tipo automatico, appunto perché la memoria viene riservata in maniera automatica dal compilatore. Per questo motivo, il compilatore deve sapere esattamente quanta memoria risevare; se, ad esempio, creiamo una variabili intera il processore riserva lo spazio esattamente per un intero. Nel caso volessimo vedere quanta memoria (in bytes) è richiesta per una variabile, possiamo utilizzare l'operatore sizeof:

// ex7_1_1.cpp
#include <iostream.h>
void main() {
  int a;
  cout << "sizeof(a) = " << sizeof(a);
}

output:
sizeof(a) = 4

Quindi, per la macchina sulla quale il programma ex7_7_1.cpp è stato eseguito, una variabile intera necessita 4 bytes 7.1. Se creiamo un array di variabili di un certo tipo, l'operatore sizeof ritorna la memoria totale occupata:


// ex7_1_2.cpp
#include <iostream.h>
void main() {
  int a[7];
  cout << sizeof(a);
}

output:
28

Come si vede, siccome una variabile intera occupa 4 bytes, un array di 7 interi ne occupa esattamente 28. Non tutte le variabili automatiche vengono distrutte alla fine del programma; ad esempio le variabili locali ad un blocco o ad una funzione vengono deallocate (cioè la memoria da loro occupate viene liberata) alla fine del blocco o della funzione; comunque sarà compito esclusivo del compilatore occuparsi dell'operazione di distruzione, così come esso si era occupato della loro creazione, quando esse non sono più utilizzabili.


// ex7_1_3.cpp
void funzione (int b) {
  int c = 17;
}

void main() {
  int a = 9;
  funzione(5);
}

In tale esempio la variabile a viene creata prima di tutte, poi vengono allocate b e c, le quali sono locali a funzione; quando il programma esce da funzione le variabili b e c non sono più accessibili al programma, come sappiamo, per cui esse vengono deallocate. Infine, un attimo prima che il programma termini del tutto, viene deallocata la variabile a. In pratica è come se il compilatore mettesse le variabili definite dall'utente una sopra l'altra, e poi cominciasse mano a mano ad aggiungere ed a togliere prendendo sempre dall'alto; tale struttura di dati è detta stack, che non a caso è il nome della zona di memoria che il compilatore riserva per le variabili automatiche di un programma.

In certi casi capita però di non sapere quante variabili sono necessarie durante l'esecuzione di un programma; ad esempio, è forse possibile prevedere quanti treni dovranno essere gestiti in una stazione ferroviaria? Sicuramente no; se dovessimo scrivere un programma che gestisca un sistema del genere, potremmo fare una stima per eccesso: è improbabile che una stazione gestisca più di 100'000 treni al giorno; tuttavia un programma del genere sarebbe del tutto inefficiente, in quanto se poi la stazione gestisce in realtà solo 2 treni al giorno, perderemmo quantità immani di spazio. Problemi del genere si presentano quotidianamente al cospetto di un programmatore, il quale ha tuttavia uno strumento utile e affidabile con il quale risolverli: l'utilizzo della memoria dinamica. A differenza dello stack, la memoria dinamica che un programma può occupare è decisa in fase di esecuzione dal programmatore, a seconda dei dati di input del programma. La memoria dinamica massima disponibile dipende esclusivamente dal quantitativo di memoria principale disponibile sulla macchina che esegue il nostro programma; nel seguito consideremo la memoria disponibile infinita, visto che l'attuale sviluppo tecnologico consente di avere immense quantità di memoria anche su calcolatori personali. La memoria dinamica (detta anche heap) è dunque una distesa di memoria della quale possiamo a nostro piacere disporre: in fase di esecuzione possiamo decidere di creare o distruggere una variabile, o un array o un puntatore o qualunque altro oggetto che abbiamo visto o vedremo in futuro.

La domanda che è possibile a questo punto porsi è la seguente: se la memoria dinamica è così flessibile e semplice da usare, perché non usare solo tale tipo di allocazione della memoria, tralasciando l'uso dello stack? Nulla vieta che un linguaggio del genere possa esistere (infatti esiste: il Java ne è un illustre esempio), tuttavia la memoria automatica è molto più efficiente di quella dinamica, a livello di velocità di esecuzione del programma; il motivo risiede nel fatto che lo stack è gestito interamente dal compilatore, il quale effettua delle ottimizzazioni su di essa perché sa esattamente le quantità di memoria con le quali lavora: per esempio è probabile che le variabili dello stack vengano allocate tutte "vicine" tra di loro. Al contrario, la memoria dinamica deve essere volta per volta allocata e deallocata, il che comporta un evidente dispendio in termini di operazioni da compiere durante l'esecuzione del programma. Essendo il C++ l'erede del C, programma principe per velocità di esecuzione dei programmi, esso non poteva esimersi dall'utilizzare la memoria automatica, che comporta come abbiamo detto una notevole efficienza. Inoltre ci sono anche altri problemi ad usare la memoria dinamica; primo fra tutti il seguente: noi consideremo lo spazio disponibile infinito, ma non è in realtà così; il programmatore si trova dunque a dovere mettere in atto delle pratiche di recupero della memoria occupata che via via non è più necessaria; tale operazione viene invece effettuata dal compilatore in maniera automatica, nel caso si allochino variabili nello stack. In generale, noi consigliamo di utilizzare ove possibile la memoria automatica, allocando dinamicamente oggetti solo ove tale operazione sia praticamente inevitabile.


next up previous contents index
Next: Gli operatori new e Up: Memoria dinamica. Strutture. Liste Previous: Memoria dinamica. Strutture. Liste   Indice   Indice analitico
Claudio Cicconetti
2000-09-06