6 Puntatori e gestione della memoria in C++

Transcript

6 Puntatori e gestione della memoria in C++
Cosa sono i puntatori?
I puntatori sono fondamentalmente delle variabili che non contengono un
valore numerico o alfanumerico, ma un puntatore (ossia un indirizzo) alla
locazione di memoria dove è memorizzato un certo valore (intero, reale o
carattere)
La definizione di una variabile puntatore avviene nello stesso modo con cui
si definisce una variabile, eccetto che dobbiamo aggiungere un’asterisco
tra il tipo e l’identificatore del puntatore (ossia il nome della variabile
puntatore)
nel nome conviene indicare una ‘p’ iniziale per ricordarci che
è un puntatore
Laboratorio di Informatica
6. Puntatori e gestione della memoria in C++
Esempi:
int* pOne;
short* pnum;
Persona* pdip;
double** ptot;
Corso di Laurea in Ingegneria Elettronica e Telecomunicazioni
A.A. 2013-2014
2° Semestre
Prof. Giovanni Pascoschi
Laboratorio di Informatica – A.A. 2013-2014
Uso dei puntatori
a cura di Pascoschi Giovanni
2
Esempio
Esempio
#include <iostream>
using namespace std;
int* pOne;
int* pTwo;
int num1, num2;
int nNumber;
int* pPointer;
int main() {
nNumber = 15;
pPointer = &nNumber;
// stampiamo il valore di nNumber
cout<< "nNumber is equal to :”<<nNumber<<endl;
// ora, alteriamo nNumber per mezzo di pPointer
*pPointer = 25;
// stampiamo il nuovo valore di nNumber
cout<<"nNumber is equal to:“<<nNumber<<endl;
return 0;
}
pOne = &num1;
pTwo = &num2;
L’operatore & deve essere letto come “l’indirizzo di” (Address-of) e
restituisce l’indirizzo di memoria della variabile e non la variabile.
L’operatore * deve essere letto come ‘il contenuto della locazione di
memoria puntata da’ a meno che non si trovi in una definizione di variabili
puntatore del tipo 'int* pOne’. L’operatore * prende il nome di operatore di
dereference (deferenziamento).
Laboratorio di Informatica – A.A. 2013-2014
// puntatore ad un int
// puntatore ad uno short int
// puntatore ad un tipo Persona (p.e. struct)
// puntatore ad un puntatore a double
a cura di Pascoschi Giovanni
3
Laboratorio di Informatica – A.A. 2013-2014
a cura di Pascoschi Giovanni
4
Puntatori : cosa succede nella memoria
Cosa succede nella memoria
Le definizioni int n=7 e int* pnum, determinano la presenza di due locazioni di
memoria allocate nell’area dati
#include <iostream>
using namespace std;
8A00
int n=7;
int* pnum;
n
int main( ) {
pnum = &n;
*pnum = 13;
return 0;
}
pnum
1 byte
7
8A01
1 byte
8A02
1 byte
8A03
1 byte
Si suppone che il tipo int richieda 2 bytes di memoria ed assumendo che un
puntatore occupi anch’esso 2 bytes. la figura sono anche indicati gli indirizzi
(in esadecimale) di ciascun byte.
L’indirizzo di n è per definizione l’indirizzo del primo byte, cioè 8A00.
L’indirizzo di pnum è 8A02.
Laboratorio di Informatica – A.A. 2013-2014
a cura di Pascoschi Giovanni
5
Cosa succede nella memoria
Laboratorio di Informatica – A.A. 2013-2014
a cura di Pascoschi Giovanni
6
Esempio
#include <iostream>
using namespace std;
Le istruzioni:
p=&n;
*p=13;
int x = - 5, y = 10;
int* p;
hanno il seguente effetto in memoria:
8A00
n
pnum
1 byte
13
8A01
8A02
8A03
Laboratorio di Informatica – A.A. 2013-2014
int main( ) {
p = &y;
x = *p;
return 0;
}
1 byte
8A00
// equivale all’istruzione x = y
1 byte
1 byte
a cura di Pascoschi Giovanni
7
Laboratorio di Informatica – A.A. 2013-2014
a cura di Pascoschi Giovanni
8
Esempio
Esempio
8A00
x
y
p
Le istruzioni:
p = &y;
x = *p;
hanno il seguente effetto in memoria
1 byte
-5
8A01
8A02
1 byte
10
8A00
1 byte
8A03
1 byte
8A04
1 byte
8A05
1 byte
8A01
9
Puntatori : casi particolari
10
1 byte
8A03
1 byte
8A04
1 byte
8A05
a cura di Pascoschi Giovanni
1 byte
8A02
y
p
Laboratorio di Informatica – A.A. 2013-2014
1 byte
10
x
8A02
Laboratorio di Informatica – A.A. 2013-2014
1 byte
a cura di Pascoschi Giovanni
10
Uso dei puntatori : compatibilità di tipo tra puntatori
float* uno, due;
// dichiarazione due puntatori a float?
NO
float* uno, float* due;
// dichiarazione due puntatori a float?
SI
Un puntatore a un tipo T può contenere solo indirizzi di variabili di tipo T.
Quindi, tipi di puntatori diversi sono incompatibili fra loro NO CASTING
Esempi
int x = 10; float * py;
py = &x;
int Intero = 10;
void* PuntatoreGenerico
/*dichiarazione puntatore generico puo’ puntare
a un qualsiasi tipo */
// non si puo’ fare //
int x = 10,int * py; float* pz;
py = &x;
pz = py;
// non si puo’ fare //
MOTIVO:l’informazione sul tipo del puntatore serve a dedurre il tipo
dell’oggetto puntato, che è indispensabile per effettuare correttamente il dereferenziamento
Laboratorio di Informatica – A.A. 2013-2014
a cura di Pascoschi Giovanni
11
Laboratorio di Informatica – A.A. 2013-2014
a cura di Pascoschi Giovanni
12
Esempio : operazioni sui puntatori
Uso dei puntatori : operazioni con i puntatori
Se P è un puntatore, ed n è un int (eventualmente anche negativo) cosa
significa un’espressione come :
#include <iostream>
using namespace std;
P+n ???
int main( ) {
int a=4, b=15;
int* pnum;
pnum=&a;
(*pnum)++;
pnum = &b;
(*pnum)- -;
cout<<a<<endl;
cout<<b<<endl;
return 0;
}
Denota un altro puntatore, che punta “n locazioni dopo” la locazione
puntata da P (“locazioni” non “byte”!)
Per “locazione” si intende l’insieme di byte necessari a memorizzare una
variabile di un certo tipo (provare a utilizzare p.e. sizeof(float) )
// incrementa a (parentesi necessarie) //
// decrementa b
// stampa 5
// stampa 14
Laboratorio di Informatica – A.A. 2013-2014
– Se int richiede 2 (o 4) byte
1 locazione = 2 bytes
– Se float richiede 4 byte
1 locazione = 4 bytes
– Se double richiede 8 byte
1 locazione = 8 bytes
a cura di Pascoschi Giovanni
13
Uso dei puntatori : operazioni con i puntatori
Laboratorio di Informatica – A.A. 2013-2014
a cura di Pascoschi Giovanni
14
Uso dei puntatori : inizializzazione dei puntatori
Se n è negativo, la locazione denotata da P+n precede in realtà quella
puntata da P
E’ buona regola non lasciare indefiniti i puntatori, per cui è bene
inizializzarli subito dopo il momento in cui vengono dichiarati
Esempio
Analogamente, se P e Q sono due puntatori allo stesso tipo T,
l’espressione P-Q denota il numero (intero) di celle che separano Q da P
(eventualmente negativo se Q precede P)
int *p;
p=0; oppure p=NULL;
• NULL è una costante simbolica dichiarata anche nella libreria <iostream>
NOTA: operazioni tra variabili puntatori a tipi diversi (P puntatore a tipo int,
Q puntatore a tipo float), come P+Q, sono illegali
Laboratorio di Informatica – A.A. 2013-2014
a cura di Pascoschi Giovanni
15
• 0 (zero) è l’unico valore int che si può assegnare ad un puntatore
Laboratorio di Informatica – A.A. 2013-2014
a cura di Pascoschi Giovanni
16
Vettori e puntatori
Vettori e puntatori
Un vettore (array) A è un insieme finito di N variabili dello stesso tipo,
ognuna identificata da un indice
Un vettore è organizzato come un puntatore costante, inizializzato a
puntare un’area di memoria opportunamente allocata al momento della
dichiarazione
Il nome del vettore A rappresenta in realtà l’indirizzo iniziale dell’area di
memoria associata al vettore stesso
Le seguenti notazioni sono equivalenti:
int vett[10];
int* vett;
• L’operatore [ ], applicato ad un nome di vettore, accede alla i-esima cella
del vettore
• L’operatore *, applicato ad un puntatore, accede alla variabile da esso
puntata
• Poiché vett è il puntatore al primo elemento del vettore, vale la relazione:
*vett = vett[0];
*(vett+1) = vett[1];
......
*(vett+n-1) = vett[n-1];
Laboratorio di Informatica – A.A. 2013-2014
a cura di Pascoschi Giovanni
17
Vettori e puntatori (aritmetica dei puntatori)
Laboratorio di Informatica – A.A. 2013-2014
a cura di Pascoschi Giovanni
18
Vettori e puntatori : esempio
Le relazioni viste per i vettori:
*A = A[0]
int Array[ ] = { 1, 2, 3, 4, 5 };
*(A+i) = A[i]
sono un’applicazione dell’aritmetica dei puntatori.
A denota il puntatore costante alla cella di memoria che contiene la prima
variabile del vettore
int* pA = Array;
// equivale a pA = &Array[0];
cout << pA[3] << endl;
// pA[3] equivale a *(pA+3);
pA[4] = 7;
// equivalente a *(pA+4) = 7;
(A+i) denota un altro puntatore che punta “i locazioni dopo” la locazione
puntata da A
*(A+i) denota il contenuto della locazione di memoria che si trova “i
locazioni dopo” la locazione puntata da A
Laboratorio di Informatica – A.A. 2013-2014
a cura di Pascoschi Giovanni
19
Laboratorio di Informatica – A.A. 2013-2014
a cura di Pascoschi Giovanni
20
Puntatori e stringhe
Array di stringhe
Una stringa si puo’ anche definire in maniera analoga al C:
Un array di stringhe si puo’ dichiarare in diversi modi:
char Array[ ] = "Una stringa";
char* pa = Array;
cout << Array << " == " << pa << endl;
// non è necessario usare *pa
cout << Array[5] << " == " << pa[5] << endl; // stampa solo un elemento
Laboratorio di Informatica – A.A. 2013-2014
a cura di Pascoschi Giovanni
21
Puntatori a puntatore
string Array[10];
// array di stringhe (stile C++)
char Array [10][20];
// matrice di caratteri
char* pArr[10];
// array di puntatori a carattere
Laboratorio di Informatica – A.A. 2013-2014
a cura di Pascoschi Giovanni
22
Puntatori a strutture
Un puntatore puo’ puntare qualunque tipo
anche un altro puntatore
Un puntatore puo’ puntare qualunque tipo
l’operatore ->
int A = 10;
anche una struttura
si usa
Esempio:
int* pA = &A;
struct data {
int** ppA = &pA;
int giorno;
A = 50;
// assegna ad A 50
*pA = 50;
// assegna ad A 50
**ppA = 50;
// assegna ad A 50
string mese;
int anno;
};
data* pdata;
data d;
pdata = &d;
(*pdata).giorno = 31;//per assegnare al campo giorno della struttura d il valore 31 opp.
pdata-> giorno = 31; //per assegnare al campo giorno della struttura d il valore 31
Laboratorio di Informatica – A.A. 2013-2014
a cura di Pascoschi Giovanni
23
Laboratorio di Informatica – A.A. 2013-2014
a cura di Pascoschi Giovanni
24
Confronto reference(riferimenti) e puntatori
Confronto reference(riferimenti) e puntatori
cout << “b = " << b << endl;
//stampa b = 5
cout << “pb = " << pb << endl;
// stampa indirizzo di b
int b = 5;
cout << "Assegnamento a pb..." << endl;
float f = 0.1;
*pb = 8;
int* pb = &b;
// si crea un puntatore a intero che punta a b
cout << “b = " << b << endl;
// stampa b = 8
int& bref = b;
// si crea una variabile alias di b
cout << “bref = " << bref << endl;
// stampa bref = 8
float& ff = f;
// si crea una variabile alias di f
cout << "Assegnamento a b..." << endl;
Laboratorio di Informatica – A.A. 2013-2014
a cura di Pascoschi Giovanni
25
Confronto reference(riferimenti) e puntatori
b = 15;
// oppure bref = 15;
cout << “b = " << b << endl;
// stampa b = 15
cout << “bref = " << bref << endl;
// stampa bref = 15
Laboratorio di Informatica – A.A. 2013-2014
a cura di Pascoschi Giovanni
26
Funzioni con parametri reference(riferimenti) e puntatori
il passaggio di parametri per indirizzo puo’ avvenire con due metodi :
reference e puntatori
dopo l'inizializzazione, un riferimento non puo’ piu’ essere associato ad
un nuovo oggetto: ogni assegnamento al riferimento si traduce in un
assegnamento all'oggetto riferito (sono alias).
// metodo per reference (riferimento)
int main( ) {
....
Un riferimento puo` essere inizializzato anche tramite un puntatore:
swap (num1, num2);
int a = 5;
....
int* pInt = &a;
int& aRef = *pInt;
}
// aRef è un alias di a tramite pInt
void swap (int& a, int& b) {
int comodo;
comodo = a;
a = b;
b = comodo;
}
Laboratorio di Informatica – A.A. 2013-2014
a cura di Pascoschi Giovanni
27
Laboratorio di Informatica – A.A. 2013-2014
a cura di Pascoschi Giovanni
28
Funzioni con parametri reference(riferimenti) e puntatori
Confronto reference e puntatori
// metodo per puntatori
Vantaggio reference : nella chiamata di una funzione non c'e` differenza
tra passaggio per valore o per riferimento
e` possibile cambiare
meccanismo senza dover modificare ne` il codice che chiama la funzione
ne` il corpo della funzione stessa; il codice è piu’ leggibile
int main( ) {
....
swap (&num1, &num2);
Svantaggio reference : il meccanismo dei reference nasconde all'utente il
fatto che si passa un indirizzo e non una copia, e cio` puo` creare grossi
problemi in fase di debugging del programma
....
}
Vantaggio puntatori: i puntatori consentono un maggior controllo sugli
accessi (tramite la keyword const per impedire la modifica) e rendono
esplicito il modo in cui il parametro viene passato (nel seguito vedremo che
in alcuni casi è piu’ conveniente usare i reference per rendere piu’ leggibile
il codice)
void swap (int* a, int* b) {
int comodo;
comodo = *a;
*a = *b;
*b = comodo;
}
Laboratorio di Informatica – A.A. 2013-2014
a cura di Pascoschi Giovanni
29
Principale utilizzo dei puntatori
Laboratorio di Informatica – A.A. 2013-2014
a cura di Pascoschi Giovanni
30
Gestione della memoria
I puntatori sono utilizzati sostanzialmente per quattro scopi:
1.
Realizzazione di strutture dati dinamiche (es. liste linkate); dopo
2.
Realizzazione di funzioni con effetti laterali sui parametri attuali;
3.
Ottimizzare il passaggio di parametri di grosse dimensioni;
4.
Rendere possibile il passaggio di parametri di tipo funzione.
A ciascun programma utente viene assegnata una porzione di memoria, che è
automaticamente divisa in quattro porzioni:
area del codice contiene il codice del programma
area statica che contiene le variabili globali e statiche
Il primo caso e` tipico di applicazioni per le quali non e` noto a priori la
quantita` di dati che si andranno a manipolare. Dichiarando un array si
pone un limite massimo al numero di oggetti di un certo tipo
immediatamente disponibili.
area heap disponibile per allocazioni dinamiche
area stack contiene i record di attivazione delle funzioni (variabili locali
e parametri).
Utilizzando i puntatori invece e` possibile realizzare ad esempio una
lista il cui numero massimo di elementi non e` definito a priori.
(p.e. anagrafe comune bari)
Laboratorio di Informatica – A.A. 2013-2014
a cura di Pascoschi Giovanni
31
Laboratorio di Informatica – A.A. 2013-2014
a cura di Pascoschi Giovanni
32
Mappa della memoria
Gestione della memoria
Area Programma
Le dimensioni delle aree Programma e Statica sono fisse e sono decise in
fase di compilazione. Tali dimensioni corrispondono al numero di righe in
linguaggio macchina del programma e al numero di variabili globali/statiche
definite in esso.
Area Statica
Le altre due aree (Heap e Stack) non hanno dimensione fissa. Tale
dimensione varia nel tempo a seconda dell’utilizzo delle due aree. In particolare
per quanto riguarda l’area Heap, essa crescerà quando verranno allocate
variabili dinamiche. La sua dimensione si ridurrà, invece, quando una o più
variabili dinamiche verranno deallocate.
Heap
Area
Dati
Stack
Laboratorio di Informatica – A.A. 2013-2014
a cura di Pascoschi Giovanni
33
Area di stack
a cura di Pascoschi Giovanni
34
Area Heap
L'area di memoria Stack (pila) é caratterizzata dalla proprietà che l'ultimo
dato che entra in memoria nella fase di scrittura è il primo ad uscire nella fase
di lettura (LIFO = Last In First Out). Appena l’esecuzione passa dal
programma chiamante a una funzione viene allocato nello Stack un pacchetto
di dati (record di attivazione).
L’area Heap è soggetta a regole di visibilità e tempo di vita completamente diverse da
quelle che governano l'area Stack e precisamente:
l'area heap 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 la definizione di un puntatore;
la sua visibilità é legata a quella della variabile puntatore che contiene il suo
indirizzo;
il suo tempo di vita coincide con l'intera durata del programma, a meno che non
venga esplicitamente deallocata; se il puntatore va in una particolare condizione,
denominata out of scope, l'area non é più accessibile, ma continua a occupare
memoria inutilmente.
Il record di attivazione contiene l'indirizzo di rientro nel programma
chiamante e la lista degli argomenti passati alla funzione. Esso viene
accatastato sopra il pacchetto precedente (quello del programma chiamante)
e poi automaticamente rimosso dalla memoria appena l'esecuzione della
funzione é terminata.
Nella memoria stack vengono memorizzate anche altre informazioni, e
precisamente i dati relativi a tutte le variabili automatiche (cioè locali e non
statiche) create dalla funzione. Il loro tempo di vita é legato all'esecuzione
della funzione proprio perché, quando la funzione termina, l'intera area stack
allocata viene rimossa.
Laboratorio di Informatica – A.A. 2013-2014
Laboratorio di Informatica – A.A. 2013-2014
a cura di Pascoschi Giovanni
35
Laboratorio di Informatica – A.A. 2013-2014
a cura di Pascoschi Giovanni
36
Gestione dinamica della memoria (operatore new)
Gestione dinamica della memoria (operatore new)
tipo é il tipo dell'oggetto (o degli oggetti) da creare;
Operatore new
Per allocare in modo dinamico la memoria il C++ mette a disposizione
l'operatore new. Questo operatore costruisce uno o più oggetti nell'area heap
e ne restituisce l'indirizzo. In caso di errore (memoria non disponibile)
restituisce NULL occorre controllare l’allocazione dello spazio di memoria.
dimensione é il numero degli oggetti, che vengono sistemati nella memoria
heap 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 dell’array;
La sintassi dell'operatore new è:
valore_iniziale é il valore con cui l'area allocata viene inizializzata (deve
essere di un tipo coerente); se é omesso l'area non é inizializzata.
new tipo[dimensione] (valore_iniziale)
N.B.: dei tre operandi dell'istruzione solo il primo è obbligatorio
Laboratorio di Informatica – A.A. 2013-2014
a cura di Pascoschi Giovanni
37
Operatore new (esempi)
Laboratorio di Informatica – A.A. 2013-2014
a cura di Pascoschi Giovanni
38
Operatore new (esempi)
Esempi :
Esempi :
int* pInt = new int;
si alloca un oggetto int nell'area heap e si usa il suo indirizzo per inizializzare il
puntatore pInt
struct persona {
int a;
string b;
};
persona* p_pers;
p_pers = new persona[100] + 20;
int* pArr = new int [10];
// allocazione di un array
si allocano 10 oggetti int nell'area heap e si usa l'indirizzo del primo oggetto per
inizializzare il puntatore pArr
In tal caso si definisce la struttura persona e si dichiara il puntatore p_pers a
questa struttura al puntatore p_pers si assegna l'indirizzo del ventesimo dei
cento oggetti di tipo persona, allocati nell'area heap
int* pMat = new int[20][20];
// allocazione di una matrice
si alloca una matrice 20x20 di interi nell’area heap e si usa l’indirizzo del primo
oggetto [0][0] per inizializzare il puntatore pMat
Laboratorio di Informatica – A.A. 2013-2014
a cura di Pascoschi Giovanni
// allocazione di una tabella
39
Laboratorio di Informatica – A.A. 2013-2014
a cura di Pascoschi Giovanni
40
Gestione dinamica della memoria (operatore delete)
Gestione della memoria
Per deallocare la memoria dell'area heap in C++ mette a disposizione
Questo operatore non restituisce alcun valore
l'operatore delete
Se l'operando punta a un'area in cui sono stati allocati più oggetti, delete va specificato
con una coppia di parentesi quadre (senza la dimensione, che il C++ é in grado di
riconoscere automaticamente).
La sintassi dell'operatore delete è:
float* pArr = new float [100] ;
delete [ ] pArr;
// alloca 100 oggetti float
// libera tutta la memoria allocata
delete nome_punt
l'operatore delete non cancella la variabile nome_punt, né altera il suo
contenuto: l'unico effetto é di liberare la memoria puntata rendendola
disponibile per ulteriori allocazioni
Laboratorio di Informatica – A.A. 2013-2014
a cura di Pascoschi Giovanni
41
Allocazione dinamica della memoria
int* punt = new int;
int a;
punt = &a;
// alloca un int nell'area heap
/*assegna a punt un indirizzo dell'area stack (out of scope),
per cui l'oggetto int dell'area heap non é più raggiungibile e
deallocabile*/
Laboratorio di Informatica – A.A. 2013-2014
a cura di Pascoschi Giovanni
42
Gestione dell’overflow di memoria
La memoria heap non è infinita
se ci si dimentica di usare l’operatore
delete e si ripete più volte l’uso dell’operatore new, si può avere out-ofmemory E’ necessario non lasciare che ciò avvenga deallocando le aree di
memoria prima che si perdano i puntatori e diventino inaccessibili
(operazione di garbage collection)
Per gestire / trappare situazioni di errore di allocazione dinamica (p.e.
mancanza di spazio nella memoria heap)
set_new_handler( ) funzione che serve a gestire gli errori dell’operatore
new, la quale ha come parametro un puntatore a funzione.
Pertanto quando si allocano degli oggetti con l’operatore new, è necessario
verificare che il puntatore restituito non è un puntatore nullo (NULL).
Laboratorio di Informatica – A.A. 2013-2014
a cura di Pascoschi Giovanni
43
Laboratorio di Informatica – A.A. 2013-2014
a cura di Pascoschi Giovanni
44
Esempio : Gestione dell’overflow di memoria
Riepilogo della lezione
#include <iostream>
#include <new>
using namespace std;
void mem_fault( );
int main( ) {
set_new_handler (mem_fault);
void mem_fault( ) {
cout<< “ Fault memoria”
<< endl;
exit(1);
}
Puntatori e gestione della memoria
Puntatori
Corretto uso dei puntatori (aritmetica dei puntatori)
Confronto tra reference e puntatori
Gestione della memoria
Allocazione dinamica della memoria in C++
Gestione overflow memoria in C++
long dim;
int* pmem;
int blocco;
cout << “Quanta memoria?”;
cin>> dim;
for (blocco = 1; ; blocco++) {
pmem = new int(dim);
cout<<“Alloc. blocco: “<<blocco<<endl;
}
}
Laboratorio di Informatica – A.A. 2013-2014
a cura di Pascoschi Giovanni
45
a cura di Pascoschi Giovanni
47
Fine della lezione
Domande?
Laboratorio di Informatica – A.A. 2013-2014
Laboratorio di Informatica – A.A. 2013-2014
a cura di Pascoschi Giovanni
46