Puntatori gestione dinamica della memoria e stringhe Come

  • Slides: 45
Download presentation
Puntatori, gestione dinamica della memoria e stringhe Come gestire direttamente l’uso della memoria di

Puntatori, gestione dinamica della memoria e stringhe Come gestire direttamente l’uso della memoria di un programma Informatica 2 Memoria dinamica

Vantaggi nell’uso dei vettori • I vettori consentono l’accesso diretto agli elementi utilizzando gli

Vantaggi nell’uso dei vettori • I vettori consentono l’accesso diretto agli elementi utilizzando gli indici: per accedere a v[i] chiediamo alla RAM il contenuto dell’indirizzo b+i*d. 678 v indirizzo v[i] = b + i × d v[i] b = indirizzo base d = dimensione elemento i = indice (o “spiazzamento”) Informatica 2 Memoria dinamica

L’accesso a un byte in un insieme di N bytes è rapido 000 0

L’accesso a un byte in un insieme di N bytes è rapido 000 0 001 1 010 011 N= 23 100 101 110 111 0 0 1 1 La RAM fornisce il contenuto della memoria b + i × d in tempo log 2(N) Informatica 2 Memoria dinamica

Problemi nell’uso dei vettori • La dimensione di un vettore v viene fissata per

Problemi nell’uso dei vettori • La dimensione di un vettore v viene fissata per sempre al momento della dichiarazione di v. Da evitare quindi: • int v[x]; /* PERICOLOSO: la dimensione di v e’ il valore corrente (magari “casuale”) di x. Inoltre cambiare x non cambia la dim. di v */ • Per inserire un nuovo elemento e nel posto i in V[0. . a-1] dobbiamo slittare di un posto tutti gli elementi dopo i: • for (j=a; j>i; j--)v[j]=v[j-1]; • v[i]=e; Informatica 2 Memoria dinamica

I puntatori • Un puntatore è una variabile il cui dominio di definizione sono

I puntatori • Un puntatore è una variabile il cui dominio di definizione sono gli indirizzi di memoria. La sua sintassi è: • • <tipo>* <variabile>; int* p; // puntatore a intero int *p, *q; /* abbreviazione di int* p; int* q; */ Informatica 2 Memoria dinamica

Dereferenziazione (*) • L’operatore *p o “dereferenziazione di p” legge/scrive dalla/sulla locazione di memoria

Dereferenziazione (*) • L’operatore *p o “dereferenziazione di p” legge/scrive dalla/sulla locazione di memoria il cui indirizzo è p p 025 fe 16 2983 *p e’ uguale a 2983 Informatica 2 Memoria dinamica

Operatore indirizzo (&) • Se x è una variabile, allora &x è l’indirizzo in

Operatore indirizzo (&) • Se x è una variabile, allora &x è l’indirizzo in cui è memorizzati il valore di x: x 025 fe 16 2983 &x vale 025 fe 16 *&x = 2983 L’operatore * è l’opposto di & Informatica 2 Memoria dinamica

Esempio di uso di *, & // esempio di dereferenziazione e // uso dell’operatore

Esempio di uso di *, & // esempio di dereferenziazione e // uso dell’operatore & #include <iostream. h> int main() {int x = 7; int *p 1, *p 2; // oppure int* p 1; int* p 2; p 1 = &x; p 2 = p 1; // p 1, p 2 sono l’indirizzo di x cout << “*p 2 = “ << *p 2 << endl; // stampa il valore di x cioe’ 7 cout << “p 2 = “ << p 2 << endl; // stampa il valore di p 2 cioe’ // l’indirizzo di x } Informatica 2 Memoria dinamica

Condivisione (o sharing) I puntatori possono condividere (in inglese: to share) l’area di memoria

Condivisione (o sharing) I puntatori possono condividere (in inglese: to share) l’area di memoria cui puntano: int *p, *q; p int n = 44; p = q = &n; q n 44 Ogni modifica del valore di n che avvenga per assegnazione su *p si riflette su n e su *q: p e q sono come dei “sinonimi” (in inglese: “alias”) di n. Questa caratteristica viene sfruttata per modificare i valori originali dei parametri attuali, simulando il passaggio per indirizzo. Informatica 2 Memoria dinamica

Sharing e alias • In C++ si possono definire “sharing” tra due aree di

Sharing e alias • In C++ si possono definire “sharing” tra due aree di memoria anche senza i puntatori, usando una operazione che costruisce sinonimi (alias): int main () { int n = 44; int& rn = n; // rn è sinonimo di n n--; cout << rn << endl; // stampa 43 } Informatica 2 Memoria dinamica

Vettori e puntatori in C++ • In C++ un vettore è una costante di

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’indirizzo di base di v, ossia p = &v[0] */ • Si può usare la notazione con gli indici per i puntatori che rappresentano vettori: • p[i] // p[i] equivale a v[i] Informatica 2 Memoria dinamica

Aritmetica dei puntatori (1) • Ad ogni tipo di dato corrisponde la dimensione in

Aritmetica dei puntatori (1) • Ad ogni tipo di dato corrisponde la dimensione in byte della memoria necessaria per contenere i suoi valori: • int sizeof(double) • // ritorna la dim. di double • I puntatori sono tipati: int *p, bool *p, double *p, … : il tipo è essenziale per sapere cosa leggere/scrivere alle locazioni di memoria cui punta p. Informatica 2 Memoria dinamica

Aritmetica dei puntatori (2) • In C++ si possono sommare (o sottrarre) interi a

Aritmetica dei puntatori (2) • In C++ si possono sommare (o sottrarre) interi a un puntatore p: l’effetto e’ aggiungere o togliere multipli di sizeof(tipo di p) • int *p, *q; q = p + 10; • /* il valore di q e’ uguale al 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] Informatica 2 Memoria dinamica

Un esempio di uso dei puntatori void Inverti (int v[]; int lun); // inverte

Un esempio di uso dei puntatori 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--) //p, q puntano agli estremi opposti di v {t = *p; *p = *q; *q = t; } } Questo programma è più efficiente del programma di inversione che usa i vettori, dato che non usa la formula v[i] = b + i × d. Non e questo, però, l’uso più importante dei puntatori. Informatica 2 Memoria dinamica

Ripasso: il passaggio dei parametri per valore void f(int n){ n++; } int a

Ripasso: il passaggio dei parametri per valore void f(int n){ n++; } int a = 0; f(a); cout << a; a 0 La chiamata f(a) e’ per valore. Essa modifica la copia n di a da 0 ad 1. Non modifica il valore originale 0 di a. Informatica 2 Memoria dinamica

Passaggio di parametri per valore void f(int n){ n++; } int a = 0;

Passaggio di parametri per valore void f(int n){ n++; } int a = 0; f(a); cout << a; a 0 n 0 Informatica 2 Memoria dinamica

Passaggio di parametri per valore void f(int n){ n++; } int a = 0;

Passaggio di parametri per valore void f(int n){ n++; } int a = 0; f(a); cout << a; a 0 n 1 Informatica 2 Memoria dinamica

Passaggio di parametri per valore void f(int n){ n++; } int a = 0;

Passaggio di parametri per valore void f(int n){ n++; } int a = 0; f(a); cout << a; a 0 Informatica 2 Memoria dinamica

Ripasso: il passaggio di parametri per riferimento void f(int& n) { n++; } int

Ripasso: il passaggio di parametri per riferimento void f(int& n) { n++; } int a = 0; f(a) cout << a; a 0 La chiamata f(a) ora e’ per riferimento. Essa crea un sinonimo n di a. Modificando n da 0 ad 1, essa modifica il valore originale di a. Informatica 2 Memoria dinamica

Passaggio di par. per riferimento void f(int& n) { n++; } int a =

Passaggio di par. per riferimento void f(int& n) { n++; } int a = 0; f(a) cout << a; a 0 n Informatica 2 Memoria dinamica

Passaggio di par. per riferimento void f(int& n) { n++; } int a =

Passaggio di par. per riferimento void f(int& n) { n++; } int a = 0; f(a) cout << a; a 1 n Informatica 2 Memoria dinamica

Passaggio di par. per riferimento void f(int& n) { n++; } int a =

Passaggio di par. per riferimento void f(int& n) { n++; } int a = 0; f(a) cout << a; a 1 Informatica 2 Memoria dinamica

Passaggio di parametri usando puntatore void f(int* pn) { (*pn)++; } int a =

Passaggio di parametri usando puntatore void f(int* pn) { (*pn)++; } int a = 0; f(&a) cout << a; a 0 Possiamo usare un puntatore pn nella chiamata f(a) per simulare una chiamata per riferimento. *pn e’ un sinonimo di a. Modificando *pn da 0 ad 1, essa modifica il valore originale di a. Informatica 2 Memoria dinamica

Passaggio di parametri usando puntatore void f(int* pn) { (*pn)++; } int a =

Passaggio di parametri usando puntatore void f(int* pn) { (*pn)++; } int a = 0; f(&a) cout << a; a 0 pn Informatica 2 Memoria dinamica

Passaggio di parametri usando puntatore void f(int* pn) { (*pn)++; } int a =

Passaggio di parametri usando puntatore void f(int* pn) { (*pn)++; } int a = 0; f(&a) cout << a; a 1 pn Informatica 2 Memoria dinamica

Passaggio di parametri usando puntatore void f(int* pn) { (*pn)++; } int a =

Passaggio di parametri usando puntatore void f(int* pn) { (*pn)++; } int a = 0; f(&a) cout << a; a 1 Informatica 2 Memoria dinamica

Allocazione dinamica della memoria e puntatori • Allocazione = destinazione di una certa quantità

Allocazione dinamica della memoria e puntatori • Allocazione = destinazione di una certa quantità di memoria per contenere valori di un dato tipo • Tutte le variabili di un programma sono allocate dal programma stesso quando sono in uso (puntatori inclusi) • Usando i puntatori, possiamo noi stessi allocare memoria durante l’esecuzione del programma. La memoria allocata da noi si trova in una area specifica, detta “memoria dinamica”. Informatica 2 Memoria dinamica

Allocazione dinamica: l’istruzione new int *p; p = new int; *p = 2983; p

Allocazione dinamica: l’istruzione new int *p; p = new int; *p = 2983; p 025 fe 16 L’istruzione new int “alloca”, cioe’ assegna al puntatore p, l’indirizzo di uno spazio di memoria libero e sufficente per contenere un intero.

Allocazione dinamica: new int *p; p = new int; *p = 2983; p 025

Allocazione dinamica: new int *p; p = new int; *p = 2983; p 025 fe 16 Informatica 2 Memoria dinamica

Allocazione dinamica: new int *p; p = new int; *p = 2983; p 025

Allocazione dinamica: new int *p; p = new int; *p = 2983; p 025 fe 16 2983 Informatica 2 Memoria dinamica

Allocazione dinamica: new float* p; *p = 3. 14159; /* ERRORE: p non è

Allocazione dinamica: new float* p; *p = 3. 14159; /* ERRORE: p non è allocato. Assegnare p significa scrivere su una memoria “casuale”, e questo ha effetti imprevedibili */ float x = 3. 14159; float *p = &x // OK: p usa l’allocazione già fatta per x float* p = new float; *p = 3. 14159; // OK: p è già allocato Informatica 2 Memoria dinamica

Allocazione dinamica di memoria in C: l’istruzione malloc Se conosciamo la dimensione in bytes

Allocazione dinamica di memoria in C: l’istruzione malloc Se conosciamo la dimensione in bytes dell’area di memoria di cui abbiamo bisogno, possiamo usare la funzione malloc Tipo *p; p = (Tipo*) malloc (sizeof(Tipo)); “malloc” alloca un’area di memoria con la dimensione in byte che gli chiediamo. In questo caso la dimensione dipende da Tipo ed è calcolata da sizeof. In caso di successo l’istruzione assegna il tipo Tipo all’area di memoria, e l’indirizzo dell’area al puntatore p. Se non c’è più memoria disponibile, malloc ritorna NULL, che sarà allora il valore di p. Informatica 2 Memoria dinamica

I puntatori e l’allocazione dinamica di un vettore Per “allocare dinamicamente un vettore v”

I puntatori e l’allocazione dinamica di un vettore Per “allocare dinamicamente un vettore v” intendiamo: posporre il momento in cui definiamo la lunghezza di v. Non è semplice: occorre conoscere: 1. Il tipo degli elementi di v; 2. il numero di questi elementi (almeno durante l’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. Una volta allocato, il vettore non è più modificabile. Informatica 2 Memoria dinamica

Un altro uso dei puntatori: stringhe • Le stringhe sono vettori di caratteri, contenenti

Un altro uso dei puntatori: stringhe • Le stringhe sono vettori di caratteri, contenenti un carattere speciale ‘’, non stampabile, detto terminatore. • char s[] = “Salve mondo”; • char s[MAXLUN]; • char *s = “Salve mondo”; • Esiste un tipo String, definito come char* : • typedef char* String; Informatica 2 Memoria dinamica

Allocazione di stringhe • E’ opportuno costruire una funzione di allocazione di memoria per

Allocazione di stringhe • E’ opportuno costruire una funzione di allocazione di memoria per le stringhe, e più in generale per ogni struttura dati che si implementa: • • • String Str. Alloc(int len) {String s = new char[len + 1]; // len + 1 per far posto a ‘’ return s; } Informatica 2 Memoria dinamica

Operazioni sulle stringhe int strlen (String s) {int n = 0; while (*s !=

Operazioni sulle stringhe int strlen (String s) {int n = 0; while (*s != ‘’) {++n; ++s; } return n; } int strcpy (String dest, String source) // PREC. lunghezza dest >= lunghezza source {while (*source != ‘’) {*dest=*source; ++dest; ++source}} Informatica 2 Memoria dinamica

I records Un record è una tupla di valori di tipi possibilmente diversi acceduti

I records Un record è una tupla di valori di tipi possibilmente diversi acceduti attraverso etichette: struct <nome struttura> {<tipo 1> <etichetta campo 1>; . . . <tipok> <etichetta campok>; } Un esempio: il tipo dei numeri razionali struct Rational {int num; int denum; } Se r ha tipo Rational, le due componenti di r si scrivono: r. num, r. denum Informatica 2 Memoria dinamica

Puntatori a record (strutture) • Come per i tipi di base e per i

Puntatori a record (strutture) • Come per i tipi di base e per i vettori, si possono definire dei puntatori a record e poi allocarli: • typedef struct Record • {int field; …} *Pointer; • Pointer r = new Record; • Data la frequenza dell’uso di puntatori a record, il C++ usa la seguente notazione abbreviata: • p->field invece di (*p). field Informatica 2 Memoria dinamica

Un modo per definire un tipo “Vettore” Possiamo definire un unico tipo Vettore per

Un modo per definire un tipo “Vettore” Possiamo definire un unico tipo Vettore per vettori di qualsiasi lunghezza. “Vettore” è il tipo Vec. Rec dei puntatori a un record di due campi, uno per la lunghezza del vettore, e l’altro per il vettore vero e proprio. typedef Vec. Rec* Vettore; typedef struct vecrec {int lun; int*vec; }Vec. Rec; //Vec. Rec = puntatori a vecrec Informatica 2 Memoria dinamica

Una rappresentazione del tipo Vettore v n = v->lun w = v->vec W[0] v

Una rappresentazione del tipo Vettore v n = v->lun w = v->vec W[0] v è un puntatore a un record di due elementi: il primo elemento v->lun è la lunghezza di un vettore W, il secondo, v->vec è l’indirizzo del primo elemento di W. W[n-1] W = v->vec ha tipo int*, è un puntatore al primo elemento W[0] di un vettore W di n elementi Informatica 2 Memoria dinamica

Come allocare un nuovo oggetto di tipo Vettore Supponiamo di volere un nuovo vettore

Come allocare un nuovo oggetto di tipo Vettore Supponiamo di volere un nuovo vettore di lunghezza n. Dobbiamo predisporre uno spazio di memoria adatto a contenerlo: Vettore Vett. Alloc (int n) { Vettore v; v = new Vec. Rec; //alloco lo spazio per “lun”, “vec” v->lun = na; v->vec = new int[n]; //alloco lo spazio per n interi return v; } Informatica 2 Memoria dinamica

Un esempio più complesso: il tipo “Matrice” (matrici dim. qualsiasi) struct Matr. Rec{int righe,

Un esempio più complesso: il tipo “Matrice” (matrici dim. qualsiasi) struct Matr. Rec{int righe, colonne; int **vecrighe; }; typedef Matr. Rec* Matrice; m: Matrice m m*: Matr. Rec r = M->righe c=M->colonne p=M->vecrighe int** p n righe di m elementi ciascuna W 0 =p[0] W 0[0] … W 0[m-1] Wn-1 =p[n-1] Wn-1[0] … Wn-1[m-1] n Informatica 2 Memoria dinamica

Come allocare un nuovo oggetto di tipo Matrice Nuova. Matrice (int r, int c)

Come allocare un nuovo oggetto di tipo Matrice Nuova. Matrice (int r, int c) { Matrice m; int i; m = new Matr. Rec; 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; } /* POSTCOND. : valore di ritorno = indirizzo del record per una matrice r x c */ Informatica 2 Memoria dinamica

Deallocazione: come riciclare la memoria che non serve più • La memoria dinamica allocata

Deallocazione: come riciclare la memoria che non serve più • La memoria dinamica allocata ma non più in uso può essere riciclata usando la funzione delete: • delete <puntatore> • Per deallocare un vettore del C (statico) non occorre ricordarne la dimensione: • delete [] <nome vettore> • Per deallocare una struttura complessa definita da noi, per esempio di tipo Matrice, occorrono tante chiamate di delete quante sono state quelle di new Informatica 2 Memoria dinamica

Deallocazione di Matrice void Dealloca. Matrice (Matrice m) { int i; //(1) deallochiamo un

Deallocazione di Matrice void Dealloca. Matrice (Matrice m) { int i; //(1) deallochiamo un vettore-riga dopo l’altro for(i = 0; i < m->righe; i++) delete [] m->vecrighe[i]; //(2) deallochiamo l’elenco dei vettori-riga delete [] m->vecrighe; // (3) deallochiamo l’indirizzo del record Matrice delete m; } Informatica 2 Memoria dinamica