Come si è visto, l'operatore & trova l'indirizzo di una variabile. In C esiste anche l'operatore inverso, che permette di accedere alla zona di memoria definita da un puntatore. Questo operatore è l'asterisco *. Il simbolo viene quindi usato in due modi distinti: per definire un tipo puntatore a un tipo (esempio: int *p), e per definire l'oggetto associato a un indirizzo, di cui ora parliamo. La regola generale è:
se p è una variabile di tipo puntatore a intero, si può pensare a *p come a una variabile di tipo intero.
È quindi possibile per esempio stampare il valore di *p, oppure usare questo valore all'interno di espressioni come fosse un intero (per esempio, 12+*p-2 è una espressione perfettamente valida). È anche possibile memorizzare dei valori in questa variabile: per esempio, *p=34; è una istruzione valida.
Nel seguente programma varpunt.c, la variabile a e la espressione *p sono esattamente equivalenti. Le loro zone di memoria sono le stesse, e quindi usare/cambiare il loro valore genera esattamente gli stessi risultati.
/* Se si assegna a p l'indirizzo di a, allora *p ed a sono la stessa cosa. */ int main() { int a=60; int *p; /* qui * indica che p e' un puntatore e non un intero */ p=&a; /* l'indirizzo di a va in p */ /* modifiche ad a sono modifiche a *p */ a=12; printf("a=%d *p=%d\n", a, *p); /* qui ----^ l'operatore * indica che la variabile non e' p ma la zona di memoria indicata da p */ /* modifiche a *p sono modifiche ad a */ *p=24312; printf("a=%d *p=%d\n", a, *p); return 0; }
Quando si usano i puntatori, è molto facile fare confusione fra oggetti puntati e i loro puntatori. Un puntatore è un indirizzo di memoria, mentre l'oggetto puntato è la zona di memoria che inizia con l'indirizzo, ed è grande quanto basta per contenere il tipo corrispondente. D'altra parte, le variabili di tipo puntatore sono anche esse variabili, ossia zone di memoria. La differenza fra una variabile int e una variabile di tipo puntatore a intero è che la prima contiene un valore intero, mentre la seconda contiene un indirizzo, e in particolare l'indirizzo iniziale della zona di memoria associata a un intero.
Consideriamo la rappresentazione della memoria come array, e vediamo cosa succede se si esegue il programma di sopra. In questa figura, e nelle successive, rappresentiamo solo la parte della memoria che ci interessa. Nel vettore sono stati messi dei valori numerici per gli indici. Questi valori sono ovviamente dei numeri di esempio: a ogni esecuzione, la posizione in memoria di una variabile può cambiare. In questo esempio, e nei successivi, si fa l'ipotesi che i puntatori siano rappresentabili con quattro byte. Questo viene fatto solo ai fini dell'esempio: non esiste nessuna garanzia che lo spazio occupato da un puntatore abbia questo valore (in generale, fare delle assunzioni sullo spazio occupato dalle variabili di un certo tipo è un errore di programmazione). |
Nella dichiarazione di a viene anche assegnato il valore 60, che viene quindi memorizzato nella corrispondente area di memoria. Non viene invece dato nessun valore iniziale a p, per cui il suo valore non è determinato.
La assegnazione dell'indirizzo di a a p fa sí che nella variabile p venga messa la prima posizione di memoria occupata da a. Nel caso dell'esempio, in p si mette il valore ef00b. Nella figura a lato si vede lo stato della memoria dopo aver eseguito la istruzione p=&a. L'espressione *p rappresenta la zona di memoria che comincia con l'indirizzo memorizzato in p. In questo caso, la variabile p contiene il valore eff0b, quindi *p rappresenta la zona di memoria che comincia con eff0b. Inoltre, p è una variabile di puntatore a intero, quindi *p rappresenta un intero, che è grande quattro byte. Si può pensare che *p è una variabile intera (grande quindi quattro byte) la cui zona di memoria inizia da eff0b. |
Nella figura a lato si vede lo stesso stato della memoria usando la rappresentazione con freccie. Il risultato finale è ovviamente lo stesso: dato che la freccia indica che la variabile p contiene l'indirizzo dia, quando si modifica/stampa *p viene modificato/stampato il valore memorizzato in a. Nella rappresentazione con freccie, *p indica quindi la zona di memoria in cui termina la freccia che inizia in p. |
Tutto questo ragionamento permette di concludere che, ovunque si usa *p, questo è equivalente ad a, nel programma di sopra. Va notato che, se a un certo punto si cambia il valore di p, questo non funziona più. Per esempio, se si fa p=&b dove b è un'altra variabile intera, allora *p diventa equivalente a b.