// 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.