Scarica I puntatori (Parte 1) e più Dispense in PDF di Fondamenti di informatica solo su Docsity!
Dispense del corso di
Fondamenti di Informatica 1
Il linguaggio C - Puntatori 1
Aggiornamento del 17/10/
I puntatori
- Abbiamo visto come, nell’architettura ADE8, sia stato necessario
introdurre il concetto di accesso indiretto per consentire di lavorare con
più dati di cui era noto solo l’indirizzo iniziale.
- Per fare questo è stato necessario utilizzare il valore contenuto in una
cella come indirizzo.
- In C non sarebbe sufficiente un semplice tipo «indirizzo». Quello che
bisogna specificare è anche il tipo di dato di cui si gestisce l’indirizzo.
- In C esiste quindi il puntatore ad un tipo di dato. Ovvero avremo
puntatori a char, puntatori ad unsigned char, puntatori a short, puntatori
ad unsigned short, puntatori a int, ...
- Un puntatore quindi è il tipo di dato che rappresenta l’indirizzo di una
variabile di un tipo ben specificato.
- La dimensione in memoria di un puntatore non dipende dal tipo di dato a
cui punta, dato che tutti gli indirizzi sono uguali e dipendono
dall’architettura.
- A meno di non cambiare il default, su Visual Studio 2017 i puntatori sono
a 32 bit.
Utilizzo dei puntatori: operatore &
- Supponiamo di avere una variabile x di tipo char e voler puntare a quella
variabile con il puntatore p. Come faccio ad assegnare a p l’indirizzo di
x? Mi serve un operatore che ritorni l’indirizzo di x:
&
- L’operatore unario & restituisce l’indirizzo della variabile a cui è applicato.
Ad esempio possiamo fare:
int main(void)
char x = 3;
char *p;
p = &x;
return 0;
- In questo modo p è una variabile di tipo puntatore che contiene l’indirizzo
di x. Si dice che «p punta a x».
Utilizzo dei puntatori: operatore *
- Ok, ma a che cosa serve avere un puntatore? Come posso cioè
utilizzarlo?
- Supponiamo ad esempio di voler leggere il valore della memoria
all’indirizzo puntato da p. Come fare? Bisogna utilizzare un operatore
che, dato il puntatore, restituisca la variabile puntata :
*
- Ma come? Non abbiamo già visto che l’asterisco serve per definire una
variabile di tipo puntatore? Purtroppo sì, cioè in C l’asterisco, come visto
per le parentesi tonde, viene utilizzato in diversi modi e a seconda del
caso assume significati diversi:
- in una espressione tipo a*b, è l’operatore binario di moltiplicazione;
- in una espressione tipo a = *b, è l’operatore unario di dereferenziazione.
Ovvero, l’espressione *b è il valore della variabile all’indirizzo b.
- In una definizione tipo int *a;, è il modificatore che definisce a come
puntatore a int.
Applicazione dei puntatori
- Riprendiamo l’esempio sbagliato che abbiamo già visto in precedenza,
dove tentavamo di scrivere una funzione in grado di raddoppiare un
valore numerico:
int y = 7; void raddoppia (int x) { x = x * 2; } int main(void) { raddoppia(y); return 0; }
- Abbiamo già detto che al momento della chiamata alla funzione
raddoppia, viene creato il parametro x della funzione inizializzato al
valore di y. Quindi modificare il valore di x non ha alcun effetto sulla cella
di memoria che chiamiamo y.
- Possiamo «legare» le due variabili trasformando x in un puntatore.
Programma errato di esempio
Applicazione dei puntatori
- La versione funzionante della funzione è la seguente: int y = 7; void raddoppia (int *x) { *x = *x * 2; } int main(void) { raddoppia(&y); return 0; }
- Non è proprio bella da leggere o da usare, ma funziona:
- Invece che passare a raddoppia il valore di y, passo l’indirizzo;
- Nella funzione dereferenzio (neologismo…) il parametro con l’operatore * e
quindi leggo e scrivo le celle di memoria in cui è contenuto y.
- Al termine della funzione, la variabile x viene distrutta, ma tanto le modifiche
sono state applicate direttamente a y.
- In generale, potendo, la soluzione che non utilizza puntatori è preferibile!
Applicazione dei puntatori
- Ecco cosa potrebbe pensare di scrivere un programmatore distratto: void swap(unsigned int a, unsigned int b) { unsigned int tmp = a; a = b; b = tmp; } unsigned int MCD(unsigned int m, unsigned int n) { if (m == 0 || n == 0) return 0; while (m != n) { if (m < n) swap(m, n); m - = n; } return m; }
- Ovviamente questa funzione non fa nulla!!! I valori di a e b vengono
scambiati, ma quelli di m e n rimarranno esattamente identici a prima.
- Quello che si deve fare è utilizzare i puntatori per modificare le variabili m
e n.
Programma errato di esempio
Applicazione dei puntatori
- La versione corretta è la seguente: void swap(unsigned int *a, unsigned int *b) { unsigned int tmp = *a; *a = *b; *b = tmp; } unsigned int MCD(unsigned int m, unsigned int n) { if (m == 0 || n == 0) return 0; while (m != n) { if (m < n) swap(&m, &n); m - = n; } return m; }
- Qui, passiamo alla funzione gli indirizzi delle variabili da scambiare e
tramite questi indirizzi, modifichiamo il contenuto della memoria di quelle
variabili.
Visualizzazione grafica dell’operazione di swap
- Viene invocata la funzione swap e questo crea le nuove variabili a e b:
m 3
MCD
n 6
a?
swap
b?
void swap(unsigned int *a, unsigned int *b){
unsigned int tmp = *a;
*a = *b;
*b = tmp;
Visualizzazione grafica dell’operazione di swap
- Viene invocata la funzione swap e questo crea le nuove variabili a e b:
- Poi avviene l’inizializzazione di a e b con &m e &n:
m 3
MCD
n 6
a
swap
b
void swap(unsigned int *a, unsigned int *b){
unsigned int tmp = *a;
*a = *b;
*b = tmp;
Visualizzazione grafica dell’operazione di swap
- Viene invocata la funzione swap e questo crea le nuove variabili a e b.
- Poi avviene l’inizializzazione di a e b con &m e &n.
- Si entra nel corpo della funzione e viene creata la variabile tmp.
- tmp viene inizializzata con *a, ovvero il valore puntato da a (3):
m 3
MCD
n 6
a
swap
b
void swap(unsigned int *a, unsigned int *b){
unsigned int tmp = *a;
*a = *b;
*b = tmp;
tmp 3
Visualizzazione grafica dell’operazione di swap
- Alla cella puntata da a si assegna il valore di quella puntata da b (6):
m 6
MCD
n 6
a
swap
b
void swap(unsigned int *a, unsigned int *b){
unsigned int tmp = *a;
*a = *b;
*b = tmp;
tmp 3
Visualizzazione grafica dell’operazione di swap
- Alla cella puntata da a si assegna il valore di quella puntata da b (6).
- Alla cella puntata da b si assegna il valore contenuto in tmp (3).
- Al termine della funzione, le variabili e i parametri vengono eliminati e si
ritorna alla funzione chiamante:
m 6
MCD
n 3
swap
void swap(unsigned int *a, unsigned int *b){
unsigned int tmp = *a;
*a = *b;
*b = tmp;
Un possibile errore
- Lavorando coi puntatori, è facile confondersi e cambiare il valore del
puntatore (cioè farlo puntare da un’altra parte) invece che il valore della
cella puntata:
void swap(unsigned int *a, unsigned int *b) { unsigned int *tmp = a; a = b; b = tmp; } unsigned int MCD(unsigned int m, unsigned int n) { if (m == 0 || n == 0) return 0; while (m != n) { if (m < n) swap(&m, &n); m - = n; } return m; }
- Questa funzione sembra fare quanto richiesto, ma all’uscita, di nuovo,
nulla è cambiato.
Programma errato di esempio