Introduzione al concetto di oggetto nel modello della memoria del C++ e ai suoi possibile tempi di vita (temporaneo, automatico, dinamico, ...). Relazione tra il tempo di vita e la visibilità (scope) di un oggetto. Gestione degli oggetti dinamici per tipi primitivi, strutture e array mediante l'utilizzo di puntatori (raw pointers).
4. Oggetti
¤ Una variabile può dunque essere definita come un
oggetto con nome
¤ RICORDA: il termine oggetto può anche essere usato
come sinonimo di istanza di una classe
¤ Le due definizioni non sono equivalenti
Un oggetto è una regione di memoria di un determinato tipo dato
4
5. Oggetti
¤ Esempio: oggetto senza nome (temporaneo) di tipo int
¤ Esempio: oggetto con nome (variabile) di tipo int
std::cout << (5+3)*3 << std::endl;
int result = (5+3)*3; // ‘result’ is the name of
// an object of type int
std::cout << result << std::endl;
5
6. Tempo di vita di un oggetto
¤ Ogni oggetto in un programma è caratterizzato da un
proprio tempo di vita (storage duration)
¤ Tempo di vita: l’intervallo di tempo che intercorre tra la
creazione dell’oggetto e la sua distruzione
6
7. Tempo di vita di un oggetto
¤ Perchè limitare il tempo di vita degli oggetti?
¤ Per preservare spazio in memoria
¤ Esempio: se una funzione non è in esecuzione, non c’è
motivo di mantenere in memoria le corrispettive variabili
locali
7
8. Tempo di vita di un oggetto
¤ A seconda del suo tempo di vita, un oggetto può essere
classificato come:
¤ Temporaneo
¤ Automatico
¤ Statico
¤ Dinamico
¤ Thread-local
8
9. Oggetti temporanei
¤ Gli oggetti temporanei contengono risultati intermedi
ottenuti durante la valutazione di un’epressione
¤ Esempio: Il risultato di y*z deve essere salvato da
qualche parte prima di poter essere aggiunto a x
int v = x + y*z;
9
10. Oggetti temporanei
¤ L’oggetto temporaneo non ha un nome
¤ La loro creazione è invisibile all’utente
¤ Non è possibile assegnargli un valore
¤ L’oggetto viene distrutto al termine della full-expression
che lo contiene
¤ Full-expression: un’espressione che non è contenuta in
un’espressione più grande
int v = x + y*z;
10
11. Oggetti automatici
¤ Un oggetto automatico viene creato al momento della
sua definizione e distrutto al termine del blocco che lo
contiene
¤ Esempio: la variabile locale x viene distrutta al termine
del blocco, ed è quindi un oggetto automatico
void do_nothing() {
int x = 12;
}
11
12. Oggetti automatici
¤ Attenzione a non confendere i due concetti:
¤ Il nome della variabile è locale: il nome è visibile
unicamente all’interno del blocco
¤ L’oggetto è automatico: la variabile verrà distrutta al
termine del blocco
una variabile locale è un oggetto automatico
12
13. Oggetti statici
¤ Un oggetto statico viene creato ad avvio del
programma e distrutto al termine del programma
¤ Esempi di oggetti statici:
¤ Variabili globali
¤ Variabili locali dichiarate come static
13
14. Oggetti statici
¤ Una variabile globale è una variabile il cui nome è visibile
in ogni punto del programma
¤ La variabile globale x è un oggetto statico, la sua vita
termina al termine del programma
int x = 10;
int main() {
x = 5; // 'x' is a global variable
std::cout << x << std::endl;
}
14
15. Oggetti statici
¤ Una variabile locale static è inizializzata a zero ad
avvio del programma, e distrutta al termine
dell’applicativo
¤ Esempio: la variabile ctr viene incrementata ad ogni
chiamata di count_calls()
size_t count_calls() {
static size_t ctr;
return ++ctr;
}
15
16. Oggetti statici
¤ il blocco che contiene una variabile locale static
determina la visibilità della variabile, ma non il suo tempo
di vita
¤ Il nome della variabile è visibile solo all’interno della
funzione, ma…
¤ La variabile viene distrutta al termine del programma, non al
termine della funzione
size_t count_calls() {
static size_t ctr;
return ++ctr;
}
16
17. Oggetti dinamici
¤ Una volta creato, un oggetto dinamico rimane in
memoria fino a quando non viene esplicitamente
richiesta la sua distruzione
¤ Il tempo di vita di un oggetto dinamico è gestito dal
programmatore, che decide:
¤ Quando creare l’oggetto dinamico
¤ Quando distruggere l’oggetto
17
18. Oggetti dinamici
int
Il programmatore
crea un oggetto
dinamico di tipo
int
5
int
L’oggetto
dinamico viene
utilizzato
5
int
Il programmatore
distrugge l’oggetto
quando non più
necessario
18
20. Processo in memoria
¤ Il sistema operativo (SO) assegna ad ogni programma in
esecuzione (processo) una porzione della memoria
disponibile
¤ Tale memoria permette l’effettiva esecuzione del
programma
¤ Esempio: una parte viene adoperata per mantenere in
memoria il valore delle variabili
20
21. Processo in memoria
¤ La porzione di memoria dedicata al processo viene
chiamata lo spazio d’indirizzamento (address space) del
processo
memoria assegnata al
programma in esecuzione
1775 3472…
21
22. Segmentazione
¤ Lo spazio d’indirizzamento di un processo è suddiviso in
quattro aree (segmenti):
¤ Code segment*
¤ Data segment
¤ Heap segment
¤ Stack segment
¤ Ogni segmento viene utilizzato per uno scopo diverso
*anche detto text segment
22
23. Segmentazione
¤ La classica rappresentazione dello spazio
d’indirizzamento è ottenuta semplicemente ruotando la
figura di 90°
memoria assegnata al
programma in esecuzione
Stack
1775 3472…
HeapDataCode
23
25. Code segment
¤ Il code segment è un’area di memoria a sola lettura che
contiene il codice che verrà eseguito
Stack
Heap
Data
movl $12, -12(%rbp)
movl $13, -16(%rbp)
movl -12(%rbp), %eax
int main() {
int a = 12, b = 13;
int c = a + b;
}
25
26. Data segment
¤ Il data segment contiene variabili globali e variabili locali
static
Stack
Heap
x: 10
ctr: 0
Code
int x = 10;
size_t count_calls() {
static size_t ctr;
return ++ctr;
}
int main() {
count_calls();
return 0;
}
26
27. Stack segment
¤ Lo stack mantiene in memoria le variabili locali ed i
parametri in ingresso alle funzioni
int main() {
int a = 15, b = 10;
return 0;
}
a: 15
b: 10
spazio rimanente nello stack
Heap
Data
Code
27
28. Stack segment
¤ Lo stack è organizzato come una pila, ogni volta che
una funzione viene invocata, viene aggiunto un nuovo
strato
¤ Il nuovo strato (frame) contiene le variabili locali ed i
parametri in ingresso della funzione appena invocata*
¤ Quando una funzione termina, il corrispettivo frame viene
rimosso dalla pila
¤ Le variabili locali vengono automaticamente distrutte
* il frame contiene anche informazioni che
permettono, una volta termina una funzione, che
il controllo ritorni alla funzione chiamante
28
29. Stack segment
void do_nothing2() {
int y = 13;
}
void do_nothing1() {
int x = 12;
do_nothing2();
}
int main() {
int a = 15, b = 10;
do_nothing1();
return 0;
}
a: 15
b: 10
x: 12
y:13
spazio
rimanente
nello stack
main()
do_nothing1()
do_nothing2()
Frame 1
Frame 2
Frame 3
29
30. Heap segment
¤ L’heap segment è l’area di memoria che contiene gli
oggetti dinamici
¤ A differenza dello stack, un nuovo oggetto può
occupare una posizione casuale all’interno dell’heap
¤ Conseguenza: non esiste nessun meccanismo che
automaticamente elimini gli oggetti non più utilizzati
Heap
Occupato
Libero
30
32. Creare oggetti dinamici
¤ Siamo al corrente di un solo modo per creare un oggetto
che sia successivamente modificabile:
¤ Assegnargli un nome, in altre parole definire una variabile
Come possiamo creare un oggetto dinamico?
32
33. Creare oggetti dinamici
¤ Assegnare un nome ad un oggetto comporta però la
definizione del suo tempo di vita
¤ Variabile locale? oggetto automatico
¤ Variabile locale static? oggetto statico
¤ variabile globale? oggetto statico
Come possiamo creare un oggetto dinamico?
33
34. Creare oggetti dinamici
¤ Un oggetto dinamico deve quindi essere anonimo, in
altre parole, non deve avere un nome
Come possiamo manipolare un oggetto senza nome?
34
35. Creare oggetti dinamici
¤ Un oggetto dinamico deve quindi essere anonimo, in
altre parole, non deve avere un nome
¤ Anche se senza nome, l’oggetto dinamico deve avere
un indirizzo
¤ IDEA: usare un puntatore per contenere l’indirizzo
dell’oggetto dinamico
Come possiamo manipolare un oggetto senza nome?
35
36. Espressione new
¤ L’espressione new permette di creare oggetti dinamici
¤ Notare come:
¤ a sia una variabile locale, dunque un oggetto automatico
¤ l’oggetto int anonimo sia invece un oggetto dinamico
int* a = new int; // 'a' is pointing to an unnamed,
// dynamic object of type int
36
37. Espressione new
¤ L’espressione new permette di creare oggetti dinamici
¤ Ricorda: la variabile a non è l’oggetto dinamico, è un
puntatore all’oggetto dinamico
int* a = new int; // 'a' is pointing to an unnamed,
// dynamic object of type int
37
39. Espressione new
¤ L’espressione new esegue due operazioni:
¤ Invoca l’operatore new, che alloca nell’heap il quantitativo
di memoria necessaria
¤ Costruisce l’oggetto nell’area di memoria designata
¤ L’espressione new restituisce l’indirizzo dell’oggetto
dinamico
int* a = new int;
39
40. Inizializzare oggetti dinamici
¤ Gli oggetti dinamici sono inizializzati a default
¤ I tipi built-in contengono quindi un valore non noto
¤ Istanze di una classe vengono inizializzati attraverso il
costruttore di default
¤ Come per gli oggetti automatici, è quindi buona norma
inizializzare gli oggetti dinamici con valori sensati
40
41. Inizializzare oggetti dinamici
¤ Sono possibili diversi tipi di inizializzazione:
¤ Inizializzazione diretta: l’oggetto puntato da d vale 12
¤ Inizializzazione per copia: l’oggetto puntato da c vale 20
¤ Inizializzazione per valore: l’oggetto puntato da e vale 0
int b = 20;
int* d = new int(12); // direct initialization
int* c = new int(b); // copy initialization
int* e = new int(); // value initialization
41
42. Modificare oggetti dinamici
¤ Per alterare oggetto dinamici si utilizza l’operatore di
indirezione *
int* a = new int;
int b = 12;
*a = b + 7;
std::cout << "the value pointed to by a is ”
<< *a << std::endl;
}
42
43. Distruggere oggetti dinamici
¤ Gli oggetti dinamici vanno esplicitamente distrutti
¤ Ogni oggetto allocato occupa parte della memoria
disponibile nell’heap
¤ La memoria non è illimitata
¤ È tecnicamente possibile saturare tutto lo spazio a
disposizione
43
44. Espressione delete
¤ L’espressione delete permette di distruggere un oggetto
dinamico
¤ Dopo la delete, l’oggetto dinamico puntato da a non
esiste più
int* a = new int;
int b = 12;
*a = b + 7;
delete a;
44
46. Espressione delete
¤ L’espressione delete esegue due operazioni:
¤ Invoca l’operatore delete, così da distrugge l’oggetto
¤ Dealloca la memoria precedentemente allocata,
rendendola così nuovamente disponibile
delete a;
46
47. Dangling pointers
¤ L’espressione delete distrugge l’oggetto dinamico
¤ Il corrispettivo puntatore non è quindi più valido
¤ Infatti, l’oggetto puntato non esiste più
¤ Il puntatore continua però a contenere l’indirizzo di dove
una volta si trovava l’oggetto dinamico
47
48. Dangling pointers
¤ L’espressione delete distrugge l’oggetto dinamico
¤ Il puntatore è diventato un dangling pointer
¤ Dangling pointer: puntatore che punta ad un area di
memoria precedentemente occupata da un oggetto non
più esistente
48
49. Dangling pointers
¤ La soluzione è quindi rafforzare il fatto che il puntatore
non sta più puntando a nulla
¤ Regola: dopo la distruzione di un oggetto dinamico, il
corrispettivo puntatore deve essere posto a nullptr (o
NULL)
delete a;
a = nullptr;
49
51. Strutture
¤ Le strutture generalizzano il concetto di array:
¤ Un array è una sequenza di elementi dello stesso tipo
¤ Una struttura è un sequenza di elementi di tipo diversi
51
Una struttura è una sequenza di elementi (membri) di tipo
arbitrario
52. Strutture
¤ Address è un tipo composto e definito dall’utente
¤ Una volta che il tipo Address è stato definito, è possibile
dichiarare variabili di tipo Address:
52
struct Address {
std::string street;
int number;
std::string town;
};
Address polimi_address;
53. Inizializzare una struttura
¤ Dal C++11, è possibile inizializzare oggetti di tipo struttura
mediante una lista di inizializzazione*
53
* prima del C++11, ciò era possibile unicamente nel caso in cui
la struttura fosse stata anche un aggregato
Address polimi_address = {"Piazza Leonardo Da Vinci",
32,
"Milan"};
54. Member access operator
¤ I membri di una struttura sono accessibili mediante
l’operatore . (member access operator)
54
polimi_address.street = "Piazza Leonardo Da Vinci";
polimi_address.number = 32;
polimi_address.town = "Milan”;
55. Strutture dinamiche
¤ È possibile dichiarare strutture dinamiche mediante
l’espressione new
¤ L’espressione new restituisce l’indirizzo della struttura
dinamica allocata nell’heap
55
56. Strutture dinamiche
¤ Esempio: la struttura polico_address è inizializzata a
default
56
Address* polico_address = new Address; // default
// initialization
58. Inizializzare una struttura dinamica
¤ Dal C++11, una struttura dinamica può essere inizializzata
con una lista di inizializzazione*
58
Address* polico_address = new Address{“Via Valleggio",
11,
”Como"};// uniform
// initialization
59. Accedere a membri di
una struttura dinamica
¤ Per accedere alla struttura dinamica è necessario:
¤ Accedere alla struttura mediante l’operatore *
¤ Accedere a un particolare membro mediante l’operatore .
59
(*polico_address).street = "Via Valleggio”;
Mediante indirezione
recuperiamo la struttura
Mediante l’operatore di
accesso alteriamo un
particolare membro della
struttura
60. Accedere a membri di
una struttura dinamica
¤ Per accessi a membri di una struttura mediante
puntatore è disponibile una variante del member access
operator
¤ L’operatore -> è talvolta indicato come
pointer-to-member operator
60
polico_address->street = "Via Valleggio”;
61. Distruggere una struttura dinamica
¤ Una struttura dinamica si distrugge mediante l’utilizzo
della espressione delete
61
delete polico_address;
63. Creare array dinamici
¤ Un array dinamico può essere creato mediante
l’espressione new[]
¤ L’espressione new restituisce l’indirizzo del primo
elemento dell’array (non avviene alcun decadimento)
¤ Nell’esempio, tale indirizzo viene salvato in un puntatore di
nome numbers
int* numbers = new int[5]; // dynamic (hence, unnamed) array
// of 5 elements of type int
63
65. Inizializzare un array dinamico
¤ Dall C++11, un array dinamico può essere inizializzato in
in modo simile ad un comune array*:
¤ Tale modalità di inizializzazione si chiama inizializzazione
uniforme (uniform initialization)
int* numbers = new int[5]{1, 7, 13, 5, 9};
65
*Differentemente dai comuni array è però sempre necessario
specificare la dimensione
66. Accedere ad un array dinamico
¤ L’accesso ad un array dinamico avviene secondo le
stesse modalità di un comune array
¤ Infatti, come sappiamo l’operatore [] è un operatore
definito sui puntatori
int* numbers = new int[5]{1, 7, 13, 5, 9};
numbers[2] = 20; // implemented as *(numbers + 2) = 20
66
67. Distruggere un array dinamico
¤ Il programmatore è responsabile della distruzione di array
dinamici non più necessari
¤ L’espressione delete[] distrugge un array dinamico
delete[] numbers;
67
68. Distruggere un array dinamico
¤ L’uso delle [] è cruciale
¤ È l’unico modo per chiedere al compilatore di distruggere
un array di oggetti, piuttosto che un singolo oggetto
¤ Se non usato, si incappa in un undefined behavior
delete[] numbers;
68
70. Dimensione di un array
¤ La dimensione di un array automatico deve essere nota
a tempo di compilazione (compile time)
¤ Tale imposizione facilita la gestione della stack segment
constexpr size_t DIM = 5;
int numbers[DIM];
70
71. Dimensione di un array
vs stack
¤ Invocare una funzione significa impilare un nuovo frame
nello stack segment
void do_nothing() {
int x = 2;
char y[2] ={5, 4};
int c = 3;
}
int main() {
int a = 12;
do_nothing();
return 0;
}
a: 12
x: 2
y[0]: 5
y[1]: 4
c: 3
spazio
rimamente
nello stack
main()
do_nothing()
71
72. Frame pointer e stack pointer
¤ Durante l’esecuzione di una funzione, possiamo
identificare due indirizzi importanti in memoria
¤ Stack pointer: l’indirizzo dell’ultima cella di memoria valida
nello stack
¤ Frame pointer: l’indirizzo della prima cella di memoria
appartenente al frame attualmente in esecuzione
72
73. Frame pointer e stack pointer
¤ Se supponiamo che do_nothing() sia in esecuzione, si
ha che:
a: 12
x: 2
y[0]: 5
y[1]: 4
c: 3
spazio
rimamente
nello stack
do_nothing()
stack pointer
frame pointer
main()
73
74. Frame pointer e stack pointer
¤ Gli indirizzi delle variabili locali della funzione in
esecuzione sono deducibili a partire dall’indirizzo
contenuto nel frame pointer
¤ Dove lo spiazzamento è misurato in byte
indirizzo var. locale = frame pointer + spiazzamento
74
75. Frame pointer e stack pointer
¤ Se supponiamo che un int occupi 2 byte, e un char 1
byte
a: 12
x: 2
y[0]: 5
y[1]: 4
c: 3
spazio
rimamente
nello stack
stack pointer
frame pointer frame pointer + 0
frame pointer + 2 byte
frame pointer + 3 byte
frame pointer + 4 byte
75
76. Dimensione di un array
¤ Se richiediamo che le variabili abbiano dimensioni note a
priori tutti gli spiazzamenti sono calcolabili a compile-time
¤ Conseguenza: è possibile conoscere a tempo di
compilazione la dimensione del frame di una funzione
¤ Questo comporta:
¤ Codice più veloce
¤ Maggiore semplicità nella scrittura dei compilatori
76
77. Array di dimensioni variabili
¤ Non possiamo usare normali array, perchè verrebbero
allocati nello stack, totalmente gestito dal compilatore
¤ Al contrario, l’heap è una porzione di memoria
totalmente a disposizione del programmatore
¤ IDEA: memorizzare l’array di dimensioni variabili
all’interno dell’heap segment
Come definire un array la cui dimensione venga
specificata a tempo d’esecuzione (run time)?
77
78. Array di dimensioni variabili
¤ Al momento della creazione di un array dinamico, la
dimensione può essere una qualsiasi espressione
¤ Non è richiesto che l’espressione sia costante
¤ Nell’esempio, dim non è una quantità costante, ma può
comunque essere usata per creare un array dinamico
size_t dim = 5;
int* numbers = new int[dim];
78
80. Array dinamici multidim.
¤ È possibile creare un array dinamico multidimensionale
attraverso l’espressione new[]
¤ L’espressione new[] restituisce l’indirizzo del primo
elemento dell’array multidim.
¤ Gli elementi di un un array multidim. sono a loro volta array
¤ Un puntatore al primo elemento è un puntatore ad array
int (*matrix)[3] = new int[3][3];
80
82. Alias per i sotto-array
¤ È possibile aumentare la leggibilità del codice definendo
un alias per il tipo “riga della matrice”
¤ La scrittura evidenzia come la new[] restituisca un
puntatore alla prima riga della matrice
using matrix_row = int[3];
matrix_row* matrix = new int[3][3];
82
83. Nascondere il tipo dei sotto-array
¤ Dal C++11, è possibile far sì che sia il compilatore a
dedurre il tipo dato di una variabile definendo la
variabile come auto
¤ Tale funzionalità può essere usata per aumentare la
leggibilità
auto matrix = new int[3][3];
83
84. Inizializzare array dinamici
multidimensionali
¤ Dal C++11, è possibile inizializzare un array dinamico
multidim. in maniera simile ad un normale array multidim.
auto matrix = new int[3][3]{{1, 7, 14},
{8, 16, 12},
{27, 32, 5}};
84
86. Array dinamici multidim.
di dimensione variabile
¤ Tutte le dimensioni dell’array, con la sola eccezione della
prima, devono essere note a tempo di compilazione
¤ matrix è un array dinamico di N elementi di tipo int[3]
¤ Essendo dinamico, N può essere noto a run time
¤ Il tipo dell’elemento (int[3]) deve però essere noto a
compile time
size_t N = 3;
auto matrix = new int[N][3] // OK
auto matrix2 = new int[3][N] // ERROR!
86
88. Bibliografia
¤ S. B. Lippman, J. Lajoie, B. E. Moo, C++ Primer (5th Ed.)
¤ B. Stroustrup, The C++ Programming Language (4th Ed.)
¤ S. Meyers, Effective C++ (2nd Ed.)
¤ R. Lafore, Object Oriented Programming in C++ (4th Ed.)
88
89. Bibliografia
¤ C++ Reference, Zero initialization
http://en.cppreference.com/w/cpp/language/zero_initializ
ation
¤ C++ Reference, Value initialization
http://en.cppreference.com/w/cpp/language/value_initiali
zation
¤ C++ Reference, new
expressionhttp://en.cppreference.com/w/cpp/language/n
ew
¤ C++ Reference, delete expression
http://en.cppreference.com/w/cpp/language/delete
89
90. Bibliografia
¤ Wikipedia, Data Segment
http://en.wikipedia.org/wiki/Data_segment
¤ University of Alberta, Understanding memory
http://www.ualberta.ca/CNS/RESEARCH/LinuxClusters/m
em.html
¤ G. Duarte, Anatomy of a program in memory
http://duartes.org/gustavo/blog/post/anatomy-of-a-
program-in-memory
90
91. Bibliografia
¤ Stackoverflow FAQ, “How do I use arrays in C++?”
http://stackoverflow.com/questions/4810664/how-do-i-
use-arrays-in-c
¤ Stackoverflow FAQ, “What are Aggregates and PODs
and how/why are they special?”
http://stackoverflow.com/q/4178175/1849221
91