Corso di Fondamenti di Informatica Ingegneria delle Comunicazioni BCOR Ingegneria Elettronica BELR Domenico Daniele Bloisi
Docenti Parte I prof. Silvio Salza salza@dis.uniroma1.it http://www.dis.uniroma1.it/~salza/fondamenti.htm Parte II ing. Domenico Daniele Bloisi, PhD bloisi@dis.uniroma1.it http://www.dis.uniroma1.it/~bloisi/didattica/fondinf1112.html Nota: %7E corrisponde alla tilde ~ Ricorsione Pagina 2
Informazioni Generali ing. Domenico Daniele Bloisi, PhD Dipartimento di Informatica e Sistemistica Via Ariosto 25 (adiacente Piazza Dante, A fermate Manzoni, Vittorio Emanuele, Tram 3 fermata via Labicana) mailto:bloisi@dis.uniroma1.it http://www.dis.uniroma1.it/~bloisi Pagina 3
Ricevimento Su appuntamento. Inviare una email per conferma. DIS, via Ariosto 25 II piano, stanza B211 Si consiglia di controllare la bacheca degli avvisi http://www.dis.uniroma1.it/~bloisi/didattica/fondinf1112.html#avvisi Pagina 4
Sommario Memoria, indirizzamento e puntatori Tipo void * e conversioni sui puntatori Gestione dinamica della memoria Tempo di vita delle variabili allocate dinamicamente Problemi di deallocazione della memoria Passaggio dei parametri tramite puntatori Pagina 5
Memoria, indirizzamento e puntatori L accesso memoria di un calcolatore avviene attraverso un meccanismo di indirizzamento. In C il programmatore ha la possibilità di gestire gli indirizzi attraverso delle variabili che vengono definite di tipo puntatore. I valori delle variabili di tipo puntatore sono indirizzi di memoria, ossia dei valori numerici che fanno riferimento a specifiche locazioni di memoria. Pagina 6
Notazione grafica L indirizzamento si rappresenta graficamente tramite una freccia. Spesso non occorre conoscere lo specifico valore di una variabile di tipo puntatore (es. 00100). Pagina 7
Operatore indirizzo-di Per ottenere dei valori di tipo puntatore, cioè degli indirizzi, si utilizza l operatore & int i = 1; printf("l\' indirizzo di i e\' " "%p\n", &i); printf("mentre il valore di i e\' " "%d\n", i); stampa L' indirizzo di i e' 0028FF1C mentre il valore di i e' 1 Pagina 8
Operatore indirizzo-di L operatore & si chiama operatore indirizzo-di e restituisce l indirizzo della variabile a cui viene applicato. Nota: i = &10; non è ammesso dato che 10 è una costante int. Analogamente non si può accedere all indirizzo di una espressione. Nota: l indicatore di conversione per un indirizzo è %p (il formato è definito dall implementazione) Pagina 9
Operatore di dereferenziamento (indirizzamento indiretto) Quando si considera una variabile di tipo puntatore, l operatore * permette di recuperare il valore della locazione di memoria puntata. int i, j = 1; i = *&j; L istruzione i = *&j; assegna (con una notazione un po complicata) il valore della variabile j alla variabile i. Equivale, in pratica, all istruzione i = j; L operatore (unario) di indirizzamento indiretto * non deve essere confuso con l operatore di moltiplicazione (binario). Pagina 10
Operatore di dereferenziamento (indirizzamento indiretto) int i, j = 1; 00100 00200 i? j 1 i = *&j; &j 00200 *(&j) Valore contenuto in 00200 1 00100 00200 i 1 j 1 Pagina 11
Operatore & VS operatore * Se x è una variabile, &x denota l indirizzo in memoria di tale variabile. nome x &x α α indirizzo Se α è l indirizzo in memoria di una variabile, *α denota tale variabile: *α x Pagina 12
Variabili di tipo puntatore Per la gestione degli indirizzi occorre dichiarare delle variabili di tipo puntatore, specificando il tipo della locazione di memoria puntata. int *p1;... // allocazione di memoria (vedi dopo) *p1 = 10; dichiara una variabile di tipo puntatore ad intero ed assegna alla variabile puntata il valore 10. Pagina 13
Attenzione 1. La dichiarazione di una variabile puntatore non alloca memoria per la variabile puntata; prima di accedere alla variabile puntata bisogna allocare esplicitamente memoria (vedremo dopo come ) 2. Nelle dichiarazioni multiple tipo: int *p1, p2; p2 non è un puntatore! Pagina 14
Esempio: uso di variabili puntatore int i, j, k; int *pt_i, *pt_j; pt_i = &i; pt_j = &j; i = 1; j = 2; k = *pt_i + *pt_j; *pt_i = 10; printf("i = %d\n", i); printf("k = %d\n", k); Pagina 15
Diagramma della memoria Le variabili di tipo int i, j, k, vengono manipolate attraverso i puntatori alle locazioni di memoria ad esse associate al momento della dichiarazione. Pagina 16
Esecuzione Il programma stampa a video: i = 10 k = 3 Pagina 17
Assegnazione di un valore specifico a puntatore #include <stdio.h> int main() { int *ptr; ptr = 1000; *ptr = 5; printf("%d\n", *ptr); } Cosa stampa questo codice? Output del compilatore warning: assignment makes pointer from integer without a cast Pagina 18
Assegnazione di un valore specifico a puntatore L assegnazione di un indirizzo specifico ad una variabile puntatore è da evitare poiché può causare il crash del programma. Pagina 19
Accessi in memoria int i, j = 2, *pt; pt = &j; i = *pt; per ottenere il valore da assegnare a i devono essere fatti 2 accessi in memoria: il primo accesso viene fatto all indirizzo di pt, per recuperare il dato ivi memorizzato, i.e. l indirizzo di j il secondo accesso avviene all indirizzo di j, per recuperare il valore memorizzato nella variabile j Pagina 20
Esempio puntatori Consideriamo gli effetti del seguente codice: int *pointer; int x = 1, y = 2; pointer = &x; y = *pointer; x = pointer; *pointer = 3; // dichiara pointer come un // puntatore a int // (1) assegna a pointer l'indirizzo // di x (i.e., pointer punta x) // (2) assegna a y il contenuto di // pointer // (3) assegna ad x l'indirizzo // contenuto in pointer // (4) assegna alla variabile // puntata da pointer il valore 3 Pagina 21
Diagramma della memoria x 32456 1 x 32456 1 y 54327 2 54327 (1) y 2 12098 pointer = &x; 12098 pointer? pointer 32456 Pagina 22
Diagramma della memoria x 32456 1 x 32456 1 y 54327 2 54327 (2) y 1 12098 y = *pointer; 12098 pointer 32456 pointer 32456 Pagina 23
Diagramma della memoria x 32456 1 x 32456 32456 y 54327 1 54327 (3) y 1 12098 x = pointer; 12098 pointer 32456 pointer 32456 Pagina 24
Diagramma della memoria x 32456 32456 x 32456 3 y 54327 1 54327 (4) y 1 12098 *pointer = 3; 12098 pointer 32456 pointer 32456 Pagina 25
Ricapitolando Possibili valori ottenibili tramite l utilizzo di variabili puntatore: pointer valore della variabile puntatore (i.e., l indirizzo della locazione di memoria a cui punta) &pointer indirizzo fisico della locazione di memoria del puntatore *pointer valore contenuto nella locazione di memoria a cui punta il puntatore Pagina 26
Operazioni sui puntatori A valori di tipo puntatore si applicano le operazioni del tipo int. Particolarmente utile è l operazione di incremento int *pti;... pt++; che consente di puntare alla successiva locazione di tipo int. L uso di queste operazioni verrà approfondito nella Unità 7 Array e Matrici Pagina 27
costanti La specifica const può essere applicata anche a variabili di tipo puntatore. double pi = 3.5; double const *pt = π (*pt)++; // OK (pi vale 4.5) pt++; // NO (pt e costante) In questo caso, la specifica const si applica al puntatore, ma non alla variabile puntata. Pagina 28
a costanti Si può anche specificare che un puntatore debba puntare a costanti. const int k = 3; const int *pt; // dichiarazione di puntatore // a costante pt = &k; // OK pt++; // ammesso anche se non si sa cosa // vada a puntare pt int *pti; pti = &k; // NO Pagina 29
a puntatori Come per ogni altro tipo si può definire un puntatore ad una variabile di tipo puntatore. double x; double * pt; double ** ptpt; x = 4; pt = &x; ptpt = &pt; printf("%f\n", **ptpt); Cosa stampa questo frammento di codice? Pagina 30
Diagramma della memoria stampa il valore di x. Pagina 31
Il puntatore NULL (1/3) Le variabili di tipo puntatore possono assumere anche un valore speciale: NULL Questo valore serve a specificare che la variabile non punta alcuna locazione di memoria. In C tale valore in genere corrisponde allo 0, ma si raccomanda di usare NULL, in particolare per verificare che ad una variabile puntatore non sia associato uno specifico riferimento. NULL è una costante simbolica in genere definita in <stdio.h> Pagina 32
Il puntatore NULL (2/3) Si faccia attenzione a non confondere variabili il cui valore è NULL con variabili non inizializzate: una variabile non inizializzata non ha alcun valore, neanche NULL. Il confronto con NULL può essere usato in una condizione di un istruzione if-else, for, etc. Pagina 33
Il puntatore NULL (3/3) Esempio int *pt = NULL; if (pt!= NULL) *pt = 10; In questo caso il ramo if non viene eseguito. L istruzione *pt = 10; eseguita al momento in cui pt vale NULL genererebbe un errore a tempo di esecuzione. Pagina 34
Il tipo void* (1/2) Mentre nel caso delle dichiarazioni dei tipi primitivi è indispensabile definire il tipo della variabile per consentire al compilatore di allocare la memoria necessaria, nel caso dei puntatori, la memoria per il puntatore è fissa (corrisponde alla dimensione di un indirizzo di memoria) e quindi si può omettere la specifica del tipo della variabile puntata. void *pt; int i; pt = &i; Pagina 35
Il tipo void* (2/2) In questi casi: non sono più ammesse le operazioni sui puntatori il puntatore assegnato ad una variabile void* non può essere assegnato ad una variabile di tipo puntatore (ad un tipo definito). Pagina 36
Conversioni su puntatori Anche nel caso delle variabili di tipo puntatore sono possibili conversioni esplicite: void * pt; int i; pt = &i; int * pti; pti = (int*)pt; // il valore viene convertito // a puntatore a int Tuttavia, l utilizzo delle conversioni sui puntatori è sconsigliato in quanto spesso provoca degli errori di programmazione. Pagina 37
Gestione dinamica della memoria Nell Unità 5 è stato presentato il modello di allocazione della memoria tramite stack, che si basa sulle regole di campo d azione (definito staticamente, cioè al momento della compilazione). Attraverso i puntatori, il C permette di utilizzare un altro modello di allocazione della memoria, che consente di definire la memoria dinamicamente, cioè al momento dell esecuzione del programma. Questa possibilità risulta particolarmente utile ed importante quando non sono note o prevedibili a priori le dimensioni dei dati in ingresso ad un programma. Pagina 38
L operatore sizeof (1/2) Il numero di byte occupati da una variabile è dato dall applicazione di sizeof. Esempio sizeof(a) è il numero di byte occupati dalla variabile a Pagina 39
L operatore sizeof (2/2) L operatore sizeof può essere applicato ad un tipo, ad un nome di variabile o ad una costante Restituisce la dimensione in byte dell oggetto passato come parametro tale calcolo viene effettuato in compilazione in base al tipo di dato che viene passato a sizeof se si incrementa un puntatore p, il suo valore numerico (indirizzo in memoria in byte) verrà incrementato di sizeof(*p) Pagina 40
stampa indirizzo, occupazione di memoria e valore int main (void) { int a = 12; char b = 'a'; float c = 0.1243; printf ("Indirizzo di a e\' %x, occupa %d bytes," " il suo valore e\' %d\n", &a, sizeof(a), a); printf ("Indirizzo di b e\' %x, occupa %d bytes," " il suo valore e\' %c\n", &b, sizeof(b), b); printf ("Indirizzo di c e\' %x, occupa %d bytes," " il suo valore e\' %f\n", &c, sizeof(c), c); return 0; } %x intero esadecimale senza segno Pagina 41
Esecuzione Esercizio 6.1 Si modifichi il codice precedente in modo da stampare la parola byte se l occupazione è pari ad 1, mentre bytes se l occupazione è > 1 Es. Indirizzo di b e' 28ff1b, occupa 1 byte, il suo valore e' a Pagina 42
Differenza tra valore e indirizzo di una variabile L indirizzo è dato in forma numerica (ma non di tipo numerico) ed è assegnato dal compilatore Il valore è assegnato dal programma Tutte le variabili dello stesso tipo occupano lo stesso numero di byte Hanno la medesima rappresentazione interna Pagina 43
Applicazione di sizeof (1/2) Essendo un operatore, sizeof può essere utilizzato ponendo l operando tra parentesi oppure anche senza l utilizzo delle parentesi Le seguenti istruzioni sono equivalenti tra loro: char a = 'r'; int size_a; size_a = sizeof a; size_a = sizeof(a); Pagina 44
Applicazione di sizeof (2/2) Un eccezione a questa possibilità si ha nel caso seguente: int size_float; size_float = sizeof(float); size_float = sizeof float; // espressione // valida // errore in // compilazione quando l operando di sizeof è il nome di un tipo di dato (float, nell esempio), le parentesi sono obbligatorie. Pagina 45
Allocazione dinamica della memoria Durante l esecuzione, un programma può richiedere esplicitamente uno spazio di memoria per immagazzinare dati. Allo stesso modo, può richiedere di rilasciare tale spazio quando non sarà più necessario. Il C offre la funzione malloc per riservare (allocare) uno spazio di memoria e la funzione free per rilasciare (deallocare) memoria. Insieme all operatore sizeof sono essenziali per l allocazione dinamica della memoria. Pagina 46
Funzione malloc La funzione malloc permette di allocare dinamicamente una porzione (chunk) di memoria. void *malloc( size_t size ); Restituisce un puntatore alla porzione di memoria di dimensione size oppure NULL se si è verificato un errore. E definita nella standard library - va inserita la direttiva #include <stdlib.h> size_t è definito come il tipo intero senza segno restituito dall operatore sizeof Pagina 47
Esempio malloc int *p; crea in memoria una variabile di tipo puntatore a int p = malloc(sizeof(int)); crea in memoria una variabile di tipo int restituisce l indirizzo della variabile creata (primo byte) assegna l indirizzo restituito a p *p = 7; la variabile di tipo int creata assume il valore 7 printf("%d", *p); viene stampato a video 7 Pagina 48
Modello di allocazione dinamica L allocazione dinamica della memoria avviene nello heap. Se lo spazio di memoria allocabile dinamicamente è esaurito viene restituito il puntatore NULL. int *pt1; // dichiarazione del puntatore ad int pt1 = malloc(sizeof(int)); // creazione dinamica // della variabile int if (pt1 == NULL) { printf("allocazione fallita\n"); exit(exit_failure); } In caso di mancanza di memoria il programma termina con l istruzione exit(exit_failure); che notifica il fallimento. Pagina 49
Funzione free La funzione free permette di deallocare la memoria allocata dinamicamente. void free( void* ptr ); free dealloca lo spazio puntato da ptr, rendendolo disponibile per usi futuri. Nota: ptr deve essere stato usato in una chiamata precedente a malloc() E definita nella standard library (<stdlib.h>) Pagina 50
Esempio free #include <stdio.h> #include <stdlib.h> int main() { int *p; double *d; p = malloc(sizeof(int)); *p = 7; printf("%d", *p); free(p); d = malloc(sizeof(double)); *d = 7; printf(" %f", *d); free(d); } Pagina 51
Esercizi Esercizio 6.2 Scrivere un programma che legga 10 numeri interi e restituisca il minimo, usando variabili di tipo puntatore ad int anziché variabili di tipo int. Esercizio 6.3 Scrivere il programma dell esercizio precedente tramite allocazione dinamica della memoria. Deallocare la memoria utilizzata prima della terminazione del programma. Pagina 52
Esercizi Esercizio 6.4 Scrivere una funzione che dato in ingresso un puntatore ne stampi la dimensione in byte, il valore, l indirizzo di memoria ed il valore della variabile puntata. Scrivere un programma che ne verifichi il comportamento. Pagina 53