Operatore di indirizzo L operatore unario di indirizzo & restituisce l indirizzo della locazione di memoria dell operando Il valore restituito non va usato come l- value (in quanto l indirizzo di memoria di una variabile non può essere assegnato in un istruzione, ma è predeterminato) &a; &(a+1); &a = b; // ok // errore // errore l indirizzo di memoria di una variabile non può essere modificato in un istruzione, ma solo usato come riferimento in quanto è predeterminato e non modificabile) Laboratorio di Informatica Antonio Monteleone 60
Puntatori Una variabile di tipo puntatore contiene l indirizzo di memoria di un altra variabile (detta variabile puntata) la quale contiene un valore specifico int main() ptr 12 24 0x7b03a928 j 12 { } int j = 12; int *ptr = &j; cout << *ptr << endl; j = 24; cout << *ptr << endl; cout << ptr << endl; return 0; Laboratorio di Informatica Antonio Monteleone 61
Dichiarazione di puntatori Nella dichiarazione si usa * int *ptr; // ptr è un puntatore a variabile di tipo int Un dato puntatore può puntare solo al tipo di variabili specificato nella dichiarazione float a = 10; int b = 12; int *ptr1; ptr1 = &a; // errore sintattico ptr1 = &b; // Ok Si può anche dichiarare un puntatore a puntatore double **ptr_ptr; La dichiarazione di un puntatore comporta allocazione di memoria per la variabile puntatore, non per la variabile puntata Laboratorio di Informatica Antonio Monteleone 62
Operazioni sui puntatori Assegnazione di un valore int i = 10; int *p = &i; int *q = p; Uso del puntatore per riferirsi all oggetto puntato *p = 3; // dereferenziazione Confronto tra puntatori (==,!=) if (p!= q) *p = 3; Laboratorio di Informatica Antonio Monteleone 63
Aritmetica dei puntatori Il valore assunto da un puntatore è un numero intero che rappresenta, in byte, un indirizzo di memoria le operazioni di somma fra un puntatore e un valore intero (con risultato puntatore), oppure di sottrazione fra due puntatori (con risultato intero) vengono eseguite tenendo conto del tipo della variabile puntata Es.: se si incrementa un puntatore a float di 3 unità, il suo valore viene incrementato di 12 byte. Se l espressione p rappresenta l indirizzo di un oggetto di tipo T, allora l espressione p + 1 rappresenta l indirizzo di un oggetto di tipo T allocato consecutivamente in memoria. Se i è un intero e p rappresenta l indirizzo addr di T che occupa n locazioni di memoria, allora l espressione p+i ha valore addr+n*i Laboratorio di Informatica Antonio Monteleone 64
L operatore di dereferenziazione L operatore unario di derefenziazione di un puntatore restituisce il valore della variabile puntata dall operando 1. Come r-value esegue un operazione di estrazione a = *p; // assegna ad a il valore della variabile puntata da p 2. Come l-value, esegue un operazione di inserimento *p = a; // assegna il valore di a alla variabile puntata da p Laboratorio di Informatica Antonio Monteleone 65
L operatore di dereferenziazione (2) L operazione di dereferenziazione è inversa a quella di indirizzo: se assegniamo a un puntatore p l indirizzo di una variabile a allora la derefenziazione *p di p coincide col valore della variabile a #include <cassert> int main() { double a = 10; double *p = &a; // assert(*p == a); return 0; } Laboratorio di Informatica Antonio Monteleone 66
L operatore di dereferenziazione (3) Puntatore nullo ptr j 12 Dangling pointer dptr a 10 #include <iostream> int main() { int j = 12; int *ptr = 0; cout << *ptr << endl; // crash! double * dptr = 0; if (j >0) { double a = 10; dptr = &a; } // a va out of scope Segmentation violation (core dumped) } cout << *dptr << endl // crash! return 0; Laboratorio di Informatica Antonio Monteleone 67
I puntatori e il qualificatore const Mediante la parola chiave const 1. E possibile specificare che un dato puntatore non deve essere usato per modificare l oggetto puntato int x = 10, y = 20; const int *ptr1 = &x; *ptr1 = 20; // errore di compilazione 2. E possibile specificare che un puntatore non deve essere modificato int * const ptr2 = &x; ptr2 = &y; // errore di compilazione Laboratorio di Informatica Antonio Monteleone 68
Puntatori e array In C++ gli array sono trattati come puntatori X[0] 1.5 X[1] X[2] X[3] X[4] 2.5 0.0 3.5 0.0 int main() { float x[5]; int j; for (j = 0; j < 5; j++) x[j] = 0; X X+1 X+3 } float *ptr = x; *ptr = 1.5; // x[0] = 1.5 *(ptr+1) = 2.5; // x[1] = 2.5 *(ptr+3) = 3.5; // x[3] = 3.5 Laboratorio di Informatica Antonio Monteleone 69
Aree di memoria In C++ è possibile distinguere due aree distinte di memoria: memoria stack e memoria heap E possibile allocare aree di memoria dinamicamente durante l esecuzione del programma ed accedere ad esse mediante puntatori. Gli oggetti così ottenuti sono detti dinamici ed allocati nella memoria libera. Laboratorio di Informatica Antonio Monteleone 70
Memoria stack L area di memoria stack é quella in cui viene allocato un pacchetto di dati non appena l esecuzione passa dal programma chiamante a una funzione. Questo pacchetto contiene: - l indirizzo di rientro nel programma chiamante - la lista degli argomenti passati alla funzione Esso viene impilato sopra il pacchetto precedente (quello del progr. chiamante) e poi automaticamente rimosso dalla memoria appena l esecuzione della funzione é terminata. Laboratorio di Informatica Antonio Monteleone 71
Memoria stack (2) Nella memoria stack vengono sistemati anche i dati relativi a tutte le variabili automatiche (cioè locali e non statiche) create dalle funzioni. Il loro tempo di vita é legato all esecuzione della funzione proprio perché, quando la funzione termina, l intera area stack allocata viene rimossa. int sum(int a[], int len) { int temp=0; for (int i=0; i<len; i++) temp += a[i]; return temp; } int main() { int vect[4] = {1,2,3,4}; int tot = sum(vect, 4); cout << somma << tot << endl; return 0; } Laboratorio di Informatica Antonio Monteleone 72
Memoria heap L area heap non é allocata automaticamente, ma solo su esplicita richiesta del programma ( allocazione dinamica della memoria) L area allocata non é identificata da un nome, ma è accessibile solo tramite dereferenziazione di un puntatore Il tempo di vita coincide con l intera durata del programma, a meno che non venga esplicitamente deallocata; se il puntatore va out of scope, l area non è più accessibile, ma continua a occupare memoria inutilmente (memory leak). Laboratorio di Informatica Antonio Monteleone 73
new e delete Glioperatorinew and delete vengono utilizzati per allocazione/deallocazione di memoria dinamica la memoria dinamica (heap), è un area di memoria libera provvista dal sistema per quegli oggetti la cui durata di vita è sotto il controllo del programmatore int *i=new int; //alloca un intero, ritorna il puntatore char *c=new char[100]; //alloca un array di 100 caratteri int *i=new int(99); //alloca un intero e lo inizializza a 99 char *c=new char( c ); //alloca un carattere inizializzato a c Int *j=new int[n][4]; //alloca un array di puntatori ad intero new riserva la quantità necessaria di memoria richiesta e ritorna l indirizzo di quest area Laboratorio di Informatica Antonio Monteleone 74
new e delete (2) L operatore delete è usato per restituire una certa area di memoria (allocata con new) allo heap Ognioggetto allocato con new deve essere distrutto con delete se non viene piu` utilizzato, altrimenti l area di memoria che esso occupata non potra` piu` essere riallocata (memory leak) L argomento di delete è tipicamente un puntatore inizializzato preventivamente con new delete ptr; delete p[i]; delete [] p; // distrugge un puntatore ad un oggetto // distrugge l oggetto p[i] // distrugge ogni oggetto di tipo p Laboratorio di Informatica Antonio Monteleone 75
Attenzione new e delete (3) la dimensione dello heap non e` infinita l allocazione con new può fallire, nel qual caso new restituisce un puntatore nullo o suscita un eccezione. Nel caso di allocazione di memoria importante bisogna verificare che l operazione abbia avuto successo prima di usare il puntatore ogni oggetto creato con new deve essere distrutto con delete, ogni oggetto creato con new[] deve essere distrutto con delete[], queste forme NON sono intercambiabili Laboratorio di Informatica Antonio Monteleone 76
Puntatori: allocazione dinamica Riferimento ad una locazione di memoria #include <iostream> ptr Attenzione: 12 int main() { int *ptr = new int; } *ptr = 12; cout << *ptr << endl; delete ptr; return 0; Non usare delete fa accumulare locazioni di memoria inutilizzate (memory leak) Utilizzare puntatori prima del new o dopo il delete causa il crash del programma Laboratorio di Informatica Antonio Monteleone 77
Puntatori: allocazione dinamica Riferimento a più locazioni di memoria #include <iostream> int main() { int *ptr = new int[3]; ptr[0] = 10; ptr[1] = 11; ptr[2] = 12 } delete [] ptr; return 0; ptr 10 11 12 Laboratorio di Informatica Antonio Monteleone 78
Regole di conversione e cast In C++ esistono conversioni esplicite ed implicite. Le conversioni implicite (e.g. int float) nelle espressioni aritmetiche, nel passare i parametri ad una funzione o nel ritornare un valore da una funzione rendono il meccanismo di conversione molto conveniente ma anche potenzialmente pericoloso (errori a run time) char, short e bool vengono promossi ad int Tipi interi che non possono essere rappresentati con un int vengono promossi a unsigned In una espressione di tipo misto, gli operandi di ordine inferiore vengono promossi all ordine superiore secondo la gerarchia: int<unsigned<long<unsigned long<float<double<long double bool e` un tipo intero, con true che viene promosso a 1 e false a 0 Laboratorio di Informatica Antonio Monteleone 79
Regole di conversione e cast (2) Ogni genere di puntatore può essere convertito in un puntatore generico a void Al contrario di quanto avviene in C, un puntatore generico non è compatibile con un puntatore di tipo arbitrario ma richiede un cast esplicito char *ch; void *generic_p;... generic_p=ch; // OK, char* va in void* ch=generic_p; // OK in C, illegale in C++ ch=(char *)generic_p; // OK, C e C++ arcaico Ogni puntatore puo` essere inizializzato a 0 senza bisogno di un cast esplicito. In C++ usare 0 e non NULL per i puntatori! Laboratorio di Informatica Antonio Monteleone 80
Casting in ANSI C++ Data la complessità delle operazioni di casting in C++ nuovi operatori di casting sono stati aggiunti a quelli già esistenti in C x=(float) i; x=float(i); // cast in C++ - notazione C // cast in C++, notazione funzionale x=static_cast<float>(i); // ANSI C++ - raccomandato i=reinterpret_cast<int>(&x) // ANSI C++, non portabile e // system dependent func(const_cast<int>(c_var)) // dove C_var e` una // variabile dichiarata // const. Usato per // eliminare la const-ness // per chiamare func Esiste anche un dynamic_cast, utilizzato per riconoscere il tipo di un oggetto a run-time (RTTI) Laboratorio di Informatica Antonio Monteleone 81
L operatore static_cast L operatore static_cast<tipo>(espressione) è usato per convertire un tipo in un altro, compatibile con il primo. int a = static_cast<int>(12.45); // da double a int L espressione a += b; // a di tipo int, b di tipo double è valutata dal compilatore come se fosse a=static_cast<int>(static_cast<double>(a)+b); Si ha lo stesso risultato esplicitando il cast a += static_cast<int>(b); Laboratorio di Informatica Antonio Monteleone 82
L operatore const_cast L operatore const_cast<tipo>(espressione) è usato per eludere l attributo const del suo operando void fun(char *s); int main() { char const hello[] = ciao ; // non altera la stringa fun(hello) // errore // cannot convert parameter 1 from 'const char [5]' to 'char * fun(const_cast<char *>(hello)); // OK } return 0; Elimina la const-ness Laboratorio di Informatica Antonio Monteleone 83
L operatore L operatore reinterpret_cast reinterpret_cast<tipo>(espressione) è usato per reinterpretare i bit che compongono il suo argomento void swap(char *c1, char *c2); // scambia i due argomenti void swap(char &c1, char &c2); // parametri per riferimento void swaplong(long *a) // scambia i byte dell argomento { char *c = reinterpret_cast<char*> (a); swap(c, c+3); // swap(c[0], c[3]); swap(c+1, c+2); // swap(c[1], c[2]); } Laboratorio di Informatica Antonio Monteleone 84