Università dell’Insubria Facoltà di Scienze Matematiche, Fisiche e Naturali di Varese Corso di Laurea in Informatica Anno Accademico 2007/08 Laboratorio di Linguaggi lezione VI: puntatori Marco Tarini 2/3 Assegnare i Puntatori • In memoria, un puntatore è un indirizzo di memoria – (...di una variabile) – (...di cui e' noto il tipo) • Bene, ma quale indirizzo? – Modo 1: prendere l'indirizzo di una variabile esistente • il puntatore punterà a quella variabile – Modo 2: allocare (riservare, prenotare) della memoria libera • il puntatore punterà ad una nuova variabile, memorizzata nella memoria così riservata • la nuova variabile è allocata dinamicamente! Marco Tarini - Laboratorio di Linguaggi - 2007/08 - Università dell’Insubri Assegnare i Puntatori • Modo 1: prendere l'indirizzo di una variabile esistente – il puntatore punterà a quella variabile • Operatore "ampersand" ( • Esempio: il puntatore p punta all'indirizzo di memoria dove vive la variabile d & ) double d = 9.0; double *p; p = &d; printf("%f",*p); *p = 21.5; printf("%f",d); scrivi il valore di *p . Cosa scrive? scrivi il valore di d . Cosa scrive? Marco Tarini - Laboratorio di Linguaggi - 2007/08 - Università dell’Insubri Operatore & e i tipi • se y è una var di tipo T... & y T T* Marco Tarini - Laboratorio di Linguaggi - 2007/08 - Università dell’Insubri Operatore errore di tipo! numeri non è mica di tipo int ! (e quindi &numeri non è di tipo int* ) & e vettori int numeri[]={10,20,30,40}; int *punt; punt = & numeri; si può scrivere invece: punt = & (numeri[0]); oppure anche: punt = numeri; scriviamo tutti e 4 i numeri: int i; for (i=0; i<4; i++) { printf("%d ", numeri[i]); } Marco Tarini - Laboratorio di Linguaggi - usando i puntatori: int i; for (i=0; i<4; i++) { printf("%d ", *(punt++)); } 2007/08 - Università dell’Insubri Altro Esempio stringa stringa[0] 'p' p stringa[1] 'u' p stringa[2] 'n' p stringa[3] 't' p stringa[4] 'a' p stringa[5] 't' p stringa[6] 'o' p stringa[7] 'r' p stringa[8] 'e' p stringa[9] 0 p char stringa[]="puntatore"; int i; while (stringa[i]) { stringa[i] = maiuscolo(stringa[i]); i++; } char stringa[]="Puntatore"; char *p = stringa; while (*p) { *p = maiuscolo( *p ); p++; } Marco Tarini - Laboratorio di Linguaggi - 2007/08 - Università dell’Insubri Uso dei Puntatori come Parametri • vi ricordate quel problemino? void raddoppia (int { x = x * 2; } x) int main(){ int incassi = 5; raddoppia( incassi ); ... } void raddoppia (int *x) { *x = *x * 2; } int main(){ int incassi = 5; raddoppia( & incassi ); ... } Remember: in C i paramatri sono passati per copia ! Marco Tarini - Laboratorio di Linguaggi - 2007/08 - Università dell’Insubri Uso dei Puntatori come Parametri • un'altra motivazione possibile: efficienza typedef struct { char nome[20]; char cognome[20]; int eta; Esami* esami_sostenuti[50]; } Persona; int eta_fra_10_anni (Persona pp) { return pp.eta + 10; } int eta_fra_10_anni (Persona * pp) { return pp->eta + 10; } Marco Tarini - Laboratorio di Linguaggi - ogni volta che si chiama questa funzione, vengono copiati sizeof(Persona) bytes. ogni volta che si chiama questa funzione, vengono copiati sizeof(Persona*) bytes. 2007/08 - Università dell’Insubri Uso dei Puntatori come Parametri • un'altra motivazione possibile: efficienza int eta_fra_10_anni (Persona pp) { return pp.eta + 10; } inefficiente int eta_fra_10_anni (Persona * pp) { return pp->eta + 10; } efficiente In questi casi, però, meglio aggiungere anche la keyword const : int eta_fra_10_anni (const Persona * pp) { return pp->eta + 10; } • più informazione presente nel codice per il programmatore (un po' come un commento) • più ottimizzazioni possibili da parte del compilatore • più controllo di errori a tempo di compilazione (per esempio se per sbaglio si tenta di cambiare il valore del parametro) Marco Tarini - Laboratorio di Linguaggi - 2007/08 - Università dell’Insubri Uso dei Puntatori come Parametri • Riassumendo: void Procedura( TipoParametro x) { ... } passaggio di parametro per copia (l'unico possibile in C) void Procedura( TipoParametro* x) { ... } void Procedura( const TipoParametro* x) { ... } Marco Tarini - Laboratorio di Linguaggi - tecnicamente, altri passaggi di parametro per copia ma ciò che si copia è un puntatore, cioè un riferimento! in pratica, (simulazione di) un passaggio di parametro per riferimento 2007/08 - Università dell’Insubri Uso dei Puntatori come Parametri • Riassumendo: void Procedura( TipoParametro x) { ... } usare quando la procedura non deve cambiare il valore del parametro void Procedura( TipoParametro* x) { ... } usare quando la procedura deve cambiare il valore del parametro void Procedura( const TipoParametro* x) { ... } Marco Tarini - Laboratorio di Linguaggi - usare quando la procedura non deve cambiare il valore del parametro ...ma sarebbe troppo oneroso fare la copia del parametro (parametro voluminoso!) 2007/08 - Università dell’Insubri Assegnare i Puntatori • In memoria, un puntatore è un indirizzo di memoria – (...di una variabile) – (...di cui e' noto il tipo) • Bene, ma quale indirizzo? – Modo 1: prendere l'indirizzo di una variabile esistente • il puntatore punterà a quella variabile – Modo 2: allocare (riservare, prenotare) della memoria libera • il puntatore punterà ad una nuova variabile, memorizzata nella memoria così riservata • la nuova variabile è allocata dinamicamente! Marco Tarini - Laboratorio di Linguaggi - 2007/08 - Università dell’Insubri Allocazione void* malloc(unsigned int n); funzione malloc ( sta per memory allocation ) 1 - alloca n bytes di memoria. 2 - restituisce l' indirizzo della memoria appena allocata • sotto forma di puntatore generico ! • " " puntatore generico, puntatore void* senza tipo, in pratica, un semplice indirizzo di memoria Marco Tarini - Laboratorio di Linguaggi - 2007/08 - Università dell’Insubri Allocazione: esempio int* p; p = malloc( ? 4 ); Ma il tipo non torna! A sx abbiamo un (int*) mentre a dx un (void*) Avviene un Typecast fra puntatori Possiamo anche renderlo esplicito (meglio): int* p; p = (int*) malloc(4); Marco Tarini - Laboratorio di Linguaggi - 2007/08 - Università dell’Insubri Typecast fra puntatori • Ogni tipo puntatore puo essere trasformato in un qualsiasi altro tipo di puntatore: (implicitamente o esplicitamente) – da int* a double* , da void* a Persona* etc. • Semantica: = “quello che fa” – L’indirizzo rimane lo stesso, cambia l’interpretazione di ciò che ci trovo – Nota: in effetti, a tempo di esecuzione, non avviene nulla! Cambiano solo cose nella testa del compilatore • Costrutto estremamente POTENTE e di basso livello (e potenzialmente molto pacciugone) Marco Tarini - Laboratorio di Linguaggi - 2007/08 - Università dell’Insubri Typecast fra puntatori: primo esempio Implicito: int* p; p = malloc( 4 ); Esplicito (meglio, più chiaro): int* p; p = (int*) malloc(4); Marco Tarini - Laboratorio di Linguaggi - 2007/08 - Università dell’Insubri Allocazione: e se la memoria finisce? void* malloc(unsigned int n); Se non c'è più memoria, l'allocazione "fallisce" e malloc restituisce il valore speciale NULL • semanticamente, NULL è un "puntatore che non punta a nulla" • NULL è rappresentato dal valore 0 Quindi, il valore resituito dalle malloc va controllato ! int* p; p = (int*) malloc(4); if (p == NULL) { /* finita memoria ... */ } Marco Tarini - Laboratorio di Linguaggi - oppure, più coincisamente if (!p) { 2007/08 - Università dell’Insubri Allocazione typedef struct { /*blah blah... un sacco di campi, array...*/ } TipoStrano TipoStrano* p; p = (TipoStrano *) malloc(sizeof(TipoStrano)); Il costrutto sizeof è estremamente utile con le malloc. Usare sempre, anche con i tipi base • int, short, float, double... • remember: il C non prescrive quanti bytes occupano! Marco Tarini - Laboratorio di Linguaggi - 2007/08 - Università dell’Insubri Dellocazione void free(void* p); libera la memoria che era stata allocata all'indirizzo p. Nota: p deve essere il risultato di una malloc! int* p; p = (int*) malloc(sizeof(int)); ... /* Qui uso (*p) */ free(p); se mi dimentico di deallocare, ho un cosiddetto memory leak Remember: non c'è alcuna garbage collection in C ! Marco Tarini - Laboratorio di Linguaggi - 2007/08 - Università dell’Insubri Allocazione e Deallocazione: esempio usando l'allocazione automatica delle variabili locali: k viene automaticamente allocato (i 4 bytes di memoria necessari al suo immagazzinamento vengono "prenotati"). int proc() { int k; k=15; k viene inizializzato (a 15) ... /* lavora con k */ return 0; all'uscita dalla procedura, i 4 bytes sono resi di nuovo disponibili }; k viene esplicitamente allocato. (a tempo di esecuzione, si trovano i 4 bytes di memoria necessari al suo immagazzinamento. La locazione viene memorizzata in k). k viene inizializzato (a 15) all'uscita dalla procedura, dobbiamo rendere i 4 bytes di nuovo disponibili esplicitamente Marco Tarini - Laboratorio di Linguaggi - usando l'allocazione dinamica: int proc() { int* k; k = (int*)malloc( sizeof(int) ); *k = 15; ... /* lavora con *k */ free(k); return 0; }; 2007/08 - Università dell’Insubri