int - Dipartimento di Informatica

Puntatori e gestione dinamica
della memoria
Corso di Informatica 2
a.a. 2003/04
Lezione 4
Ugo de'Liguoro - Informatica 2 a.a. 03/04 Lez. 4
Vantaggi nell’uso dei vettori
• Sono legati all’accesso diretto agli elementi
utilizzando gli indici.
d = dimensione
elemento

v
indirizzo v[i] = b + i  d
v[i]
b = indirizzo base
i = indice (spiazzamento)
Ugo de'Liguoro - Informatica 2 a.a. 03/04 Lez. 4
Indirizzamento della RAM
000
0
001
1
010
011
23
100
101
110
0
0
1
0
0
1
1
0
1
0
1
111
1
Ugo de'Liguoro - Informatica 2 a.a. 03/04 Lez. 4
Problemi nell’uso dei vettori
• La dimensione di un vettore deve essere
fissata al momento della compilazione:
int v[100];
// non int v[dim]; con dim variabile
• L’inserimento di un nuovo valore comporta
lo slittamento di tutti quelli alla sua destra:
for (j = indice_prox_loc_libera;
v[j] = v[j-1];
v[i] = nuovo_valore;
j > i; j--)
Ugo de'Liguoro - Informatica 2 a.a. 03/04 Lez. 4
I puntatori
• Un puntatore è una variabile il cui dominio
di definizione sono gli indirizzi di memoria
<tipo>* <variabile>;
int* p; // puntatore a intero
int *p, *q;
// breve per int* p; int* q;
Ugo de'Liguoro - Informatica 2 a.a. 03/04 Lez. 4
Dereferenziazione (*)
•
L’operatore di dereferenziazione (* <puntatore>)
legge/scrive alla locazione di memoria il cui indirizzo è il valore
del suo operando
p
025fe16
025fe16
2983
*p == 2983
Ugo de'Liguoro - Informatica 2 a.a. 03/04 Lez. 4
Operatore indirizzo (&)
• Se x è una variabile, &x è l’indirizzo a cui
sono memorizzati i suoi valori
025fe16
x
2983
&x == 025fe16
Ugo de'Liguoro - Informatica 2 a.a. 03/04 Lez. 4
Esempio
// esempio di dereferenziazione e
// uso dell’oporatore &
#include <iostream.h>
int main()
{
int x = 7;
int *p1, *p2; // oppure int* p; int* q;
p1 = &x;
p2 = p1;
cout << “*p2 = “ << *p2 << endl;
// stampa il valore di x cioe’ 7
cout << “p2 = “ << p2 << endl;
// stampa il valore di p2 cioe’
// l’indirizzo di x
}
Ugo de'Liguoro - Informatica 2 a.a. 03/04 Lez. 4
Condivisione (sharing)
I puntatori possono condividere l’area di memoria cui puntano:
int *p, *q;
int n = 44; p = q = &n;
p
q
n
44
Ogni modifica del valore di n che avvenga per assegnazione
su *p si riflette su n e su *q. Questa caratteristica viene
sfruttata per ottonere effetti collaterali sui valori dei parametri
attauli, passandoli cioè per indirizzo.
Ugo de'Liguoro - Informatica 2 a.a. 03/04 Lez. 4
Riferimenti
In C++ si può avere sharing anche senza i
puntatori, usando riferimenti, ovvero sinonimi
(alias):
<tipo>& <nome rif> = <nome var>
int main ()
{
int n = 44;
int& rn = n; // rn è sinonimo di n
n--;
cout << rn << endl; // stampa 43
}
Ugo de'Liguoro - Informatica 2 a.a. 03/04 Lez. 4
Vettori e puntatori in C++
• In C++ un vettore è una costante di tipo
puntatore:
int v[100]; int* p;
p = v; // il valore di p è l’ind. di base di v
// ossia p == &v[0]
• Si può usare la notazione con gli indici per
i puntatori che si riferiscono a vettori:
p[i] … // equivale a v[i]
Ugo de'Liguoro - Informatica 2 a.a. 03/04 Lez. 4
Aritmetica dei puntatori (1)
• Ad ogni tipo di dato corrisponde la dimensione
in byte della memoria necessaria per contenere
i suoi valori:
int sizeof(<tipo>) // C: ritorna la dim.
• I puntatori sono tipati: ciò è essenziale per
sapere cosa leggere/scrivere alle locazioni di
memoria cui puntano
Ugo de'Liguoro - Informatica 2 a.a. 03/04 Lez. 4
Aritmetica dei puntatori (2)
• In C++ si possono sommare (o sottrarre)
interi a puntatori:
int *p, *q; q = p + 10;
// il valore di q ==
// valore di p + 10*sizeof(int)
Quindi, se p punta ad un vettore v:
p+i == &v[i] // ovvero
*(p+i) == v[i] == p[i]
Ugo de'Liguoro - Informatica 2 a.a. 03/04 Lez. 4
Aritmetica dei puntatori (3)
• incremento e decremento:
int *p; p = &n; p++;
/* p punta a &n + sizeof(int)
*/
• somma e sottrazione di un intero:
int n, m, *p; p = &n; p = p + m;
/* p punta a &n + m * sizeof(int) */
• differenze tra puntatori:
int n, a, b, *p, *q; p = &a, q = &b; n = p - q;
/* n è il numero degli interi allocabili tra
gli indirizzi di a e di b */
• confronto tra puntatori:
int n, m, *p; p = &n; q = &m;
if (p < q) … /* eseguito se l’indirizzo di n
è minore di quello di m */
Ugo de'Liguoro - Informatica 2 a.a. 03/04 Lez. 4
Esempio
void Inverti (int v[]; int lun);
// inverte l’ordine degli el. di v[lun]
{
int t, *p, *q;
for(p = v, q = p + (lun-1); p < q; p++, q--)
{
t = *p; *p = *q; *q = t;
}
}
Ugo de'Liguoro - Informatica 2 a.a. 03/04 Lez. 4
Passaggio di parametri per valore
void f(int n){ n++; }
int a = 0;
f(a);
cout << a;
a
0
Ugo de'Liguoro - Informatica 2 a.a. 03/04 Lez. 4
Passaggio di parametri per valore
void f(int n){ n++; }
int a = 0;
f(a);
cout << a;
a
0
n
0
Ugo de'Liguoro - Informatica 2 a.a. 03/04 Lez. 4
Passaggio di parametri per valore
void f(int n){ n++; }
int a = 0;
f(a);
cout << a;
a
0
n
1
Ugo de'Liguoro - Informatica 2 a.a. 03/04 Lez. 4
Passaggio di parametri per valore
void f(int n){ n++; }
int a = 0;
f(a);
cout << a;
a
0
Ugo de'Liguoro - Informatica 2 a.a. 03/04 Lez. 4
Passaggio di par. per riferimento
void f(int& n) { n++; }
int a = 0;
f(a)
cout << a;
a
0
Ugo de'Liguoro - Informatica 2 a.a. 03/04 Lez. 4
Passaggio di par. per riferimento
void f(int& n) { n++; }
int a = 0;
f(a)
cout << a;
a
0
n
Ugo de'Liguoro - Informatica 2 a.a. 03/04 Lez. 4
Passaggio di par. per riferimento
void f(int& n) { n++; }
int a = 0;
f(a)
cout << a;
a
1
n
Ugo de'Liguoro - Informatica 2 a.a. 03/04 Lez. 4
Passaggio di par. per riferimento
void f(int& n) { n++; }
int a = 0;
f(a)
cout << a;
a
1
Ugo de'Liguoro - Informatica 2 a.a. 03/04 Lez. 4
Passaggio di par. per puntatore
void f(int* pn) { *(pn++); }
int a = 0;
f(&a)
cout << a;
a
0
Ugo de'Liguoro - Informatica 2 a.a. 03/04 Lez. 4
Passaggio di par. per puntatore
void f(int* pn) { *(pn++); }
int a = 0;
f(&a)
cout << a;
0
a
pn
Ugo de'Liguoro - Informatica 2 a.a. 03/04 Lez. 4
Passaggio di par. per puntatore
void f(int* pn) { *(pn++); }
int a = 0;
f(&a)
cout << a;
1
a
pn
Ugo de'Liguoro - Informatica 2 a.a. 03/04 Lez. 4
Passaggio di par. per puntatore
void f(int* pn) { *(pn++); }
int a = 0;
f(&a)
cout << a;
a
1
Ugo de'Liguoro - Informatica 2 a.a. 03/04 Lez. 4
Allocazione dinamica
• Allocazione = destinazione di una certa
quantità di memoria per contenere valori di
un dato tipo
• Tutte le variabili di un programma sono
allocate quando sono in uso (puntatori
inclusi)
• E’ possibile allocare memoria durante
l’esecuzione del programma in una area
specifica detta “memoria dinamica”
Ugo de'Liguoro - Informatica 2 a.a. 03/04 Lez. 4
Allocazione dinamica: new
int *p;
p = new int;
*p = 2983;
p
025fe16
Ugo de'Liguoro - Informatica 2 a.a. 03/04 Lez. 4
Allocazione dinamica: new
int *p;
p = new int;
*p = 2983;
p
025fe16
025fe16
Ugo de'Liguoro - Informatica 2 a.a. 03/04 Lez. 4
Allocazione dinamica: new
int *p;
p = new int;
*p = 2983;
p
025fe16
025fe16
2983
Ugo de'Liguoro - Informatica 2 a.a. 03/04 Lez. 4
Allocazione dinamica: new
float* p;
*p = 3.14159;
// ERRORE: p non è allocato
float x = 3.14159;
float *p = &x
// OK: p usa l’allocazione di x
float* p = new float;
*p = 3.14159;
// OK: p è allocato
Ugo de'Liguoro - Informatica 2 a.a. 03/04 Lez. 4
Allocazione dinamica in C
Tipo *p;
p = (Tipo*) malloc (sizeof(Tipo));
Alloca una struttura dati la cui dimensione in byte dipende da
Tipo ed è calcolata da sizeof; in caso di successo assegna
il tipo Tipo* all’indirizzo di inizio del blocco, ed il valore è
salvato in p.
Se non c’è più memoria disponibile, malloc ritorna NULL,
che sarà allora il valore di p.
Ugo de'Liguoro - Informatica 2 a.a. 03/04 Lez. 4
Allocazione dinamica di un vettore
Per allocare dinamicamente un vettore occorre conoscere:
1. Il tipo dei suoi elementi;
2. il numero di questi elementi (che tuttavia potrà essere noto
anche solo al momento dell’esecuzione).
int *v, lun;
v = new int[lun]; // in C++
v = (int*) malloc (sizeof(int)*lun); // in C
Alloca un vettore di lun interi, dove però lun è una variabile.
Comunque, una volta allocato, v punterà ad un vettore la cui
lunghezza non è più modificabile.
Ugo de'Liguoro - Informatica 2 a.a. 03/04 Lez. 4
Le stringhe
• Le stringhe sono vettori di caratteri,
contenenti un terminatore: ‘\0’
char s[] = “Salve mondo”;
char s[MAXLUN];
char *s = “Salve mondo”;
• Esiste un tipo stringa, definito:
typedef char* String;
Ugo de'Liguoro - Informatica 2 a.a. 03/04 Lez. 4
Allocazione di stringhe
• E’ opportuno costruire una funzione di
allocazione (allocatore o generatore) per
ogni struttura dati che si implementa:
String StrAlloc(int len)
{
String *s;
s = new String[len + 1];
// len + 1 per far posto a ‘\0’
return s;
}
Ugo de'Liguoro - Informatica 2 a.a. 03/04 Lez. 4
Operazioni sulle stringhe (1)
int strlen (String s)
{
int n = 0;
while (*s++ != ‘\0’) n++;
return n;
}
int strcpy (String dest; String source)
{
while((*dest++ = *source++)!= ‘\0’);
}
Ugo de'Liguoro - Informatica 2 a.a. 03/04 Lez. 4
Operazione sulle stringhe (2)
String LeggiStringa (void)
{
char buffer[MAXLUN]; String s;
gets(buffer);
s = Stralloc(strlen(buffer));
strcpy(s, buffer);
return s;
}
Ugo de'Liguoro - Informatica 2 a.a. 03/04 Lez. 4
I record
Un record è una tupla di valori di tipi possibilmente diversi
acceduti attraverso etichette:
struct <nome struttura> {
<tipo1> <etichetta campo1>;
...
<tipok> <etichetta campok>;
}
Ugo de'Liguoro - Informatica 2 a.a. 03/04 Lez. 4
Allocazione dinamica di record
(strutture)
• Come per i tipi di base e per i vettori, si
possono allocare record:
typedef struct Record
{int field; …} *MyRecord;
MyRecord = new Record;
• Data la frequenza dell’uso di puntatori a
record, il C++ usa la sintassi:
p->field invece di (*p).field
Ugo de'Liguoro - Informatica 2 a.a. 03/04 Lez. 4
Un modo per definire il tipo vettore (1)
• Un buon metodo per definire un vettore da
allocare dinamicamente è usare un record
con un campo lunghezza:
typedef VecRec* Vettore;
typedef struct vecrec {
int lun;
int* vec;
} VecRec;
Ugo de'Liguoro - Informatica 2 a.a. 03/04 Lez. 4
Un modo per definire il tipo vettore (2)
v
n

n – elementi del vettore
Ugo de'Liguoro - Informatica 2 a.a. 03/04 Lez. 4
Un modo per definire il tipo vettore (3)
Vettore VettAlloc (int lunghezza)
{
Vettore v;
v = new VecRec;
v->lun = lunghezza;
v->vec = int[lunghezza];
return v;
}
Ugo de'Liguoro - Informatica 2 a.a. 03/04 Lez. 4
Un esempio più complesso: matrici (1)
typedef MatrRec* Matrice;
typedef struct matrmec {
int righe, colonne;
int **vecrighe;
} MatrRec;
n
m
m

n
Ugo de'Liguoro - Informatica 2 a.a. 03/04 Lez. 4
Un esempio più complesso: matrici (2)
Matrice NuovaMatrice (int r, int c)
{ Matrice m; int i;
m = new MatrRec;
m->righe = r; m->colonne = c;
m->vecrighe = new int*[r];
for (i = 0; i < r; i++)
m->vecrighe[i] = new int[c];
return m;
}
Ugo de'Liguoro - Informatica 2 a.a. 03/04 Lez. 4
Deallocazione
• La memoria dinamica allocata può essere
recuperata usando la funzione delete:
delete <puntatore>
• Per deallocare un vettore non occorre
ricordarne la dimensione:
delete [] <nome vettore>
• Per deallocare una struttura complessa come
matrice occorrono tante chiamate di
delete quante sono state quelle di new
Ugo de'Liguoro - Informatica 2 a.a. 03/04 Lez. 4
Deallocazione di Matrice
void DeallocaMatrice (Matrice m)
{
int i;
for(i = 0; i < m->righe; i++)
delete [] m->vecrighe[i];
delete [] m->vecrighe;
delete m;
}
Ugo de'Liguoro - Informatica 2 a.a. 03/04 Lez. 4