Usare l'oggetto puntato

  1. se p è un puntatore, allora *p è come se fosse una variabile
  2. esempio di uso dell'operatore *
  3. cosa succede in memoria quando si esegue un programma con puntatori
  4. rappresentazione degli indirizzi con freccie

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.