Passaggio di un puntatore a una funzione

  1. si può passare l'indirizzo di una variabile a una funzione
  2. effetti della modifica a) del valore della zona di memoria puntata, e b) del valore del parametro formale stesso
  3. cosa succede in memoria

La prima applicazione dei puntatori è quello del passaggio dei parametri a una funzione. Quando si passa un numero a una funzione, quello che succede in effetti è che:

  1. si crea una nuova variabile con il nome del parametro formale;
  2. in questa nuova variabile si copia il valore del parametro attuale;
  3. si esegue il corpo della funzione.

Questo significa che, se si chiama una funzione passando una variabile come parametro, quello che viene in effetti usato dalla funzione è il valore della variabile, che viene copiato in una zona di memoria nuova (il parametro formale). Se quindi si modifica il parametro formale, queste modifiche vengono fatte nella nuova zona di memoria, distinta da quella del parametro attuale. Quindi, il programma che ha chiamato la funzione non vede le modifiche, semplicemente perchè le modifiche non sono state fatta in una zona di memoria che corrisponde a una sua variabile.

Vediamo ora cosa succede se si passa un puntatore ad una funzione. Consideriamo il seguente programma passaggio.c, in cui la funzione esempio prende come parametri un intero e un puntatore a intero.

/*
  Una funzione che riceve un puntatore e un intero.
*/

#define NULL 0

void esempio(int a, int *b) {
  a=12;

  *b=3;

  b=NULL;
}

int main() {
  int x;
  int y;
  int *z;

  x=10;
  y=43;
  z=&y;

  printf("Valori: x=%d y=%d z=%x\n", x, y, z);

  esempio(x, z);

  printf("Valori: x=%d y=%d z=%x\n", x, y, z);

  return 0;
}

La funzione esempio fa tre cose: cambia il valore della variabile intera a=12;, cambia il valore dell'oggetto puntato dal puntatore *b=3;, e cambia anche il valore del puntatore stesso b=NULL.

Compilando ed eseguendo il programma si ottiene la seguente stampa:

Valori: x=10 y=43 z=bffff448
Valori: x=10 y=3 z=bffff448

Questo significa che i valori di x e z non sono cambiati, mentre quello di y sí. Per capire questo comportamento, vediamo lo stato della memoria passo per passo.

Stato della memoria prima della chiamata a funzione: x, y e z sono tre variabili, ognuna associata a una zona di memoria. Il valore di z è l'indirizzo di y. In altre parole, nella variabile z è memorizzata la posizione di memoria in cui y inizia.

Stato della memoria all'inizio della esecuzione della funzione. Sono state create due nuove variabili a e b, in cui sono stati copiati i valori degli argomenti con cui è stata chiamata la funzione. In questo caso, la funzione è stata chiamata con esempio(x,z). Quindi, nella variabile a è stato messo il valore di x, cioè 10. Nella variabile b è stato messo il valore di z. Si noti che il valore di z non è 43; il valore di z è l'indirizzo a cui è memorizzata la variabile y. Questo valore viene copiato in b, che quindi ora contiene l'indirizzo della variabile y.

Durante la esecuzione della funzione, viene messo il valore 12 nella variabile a. La seconda istruzione mette 3 nella zona di memoria puntata da b. Dal momento che b punta alla zona di memoria in cui è memorizzato y, la situazione alla fine delle prime due istruzioni è quella riportata qui accanto.

L'ultima istruzione della funzione mette NULL nel puntatore b. Alla fine della esecuzione della funzione la situazione è quindi quella accanto. A questo punto, la funzione termina e le variabili a e b vengono deallocate.

Vediamo ora cosa è successo alle variabili del programma principale. Le variabili x e z non sono state cambiate. L'unica variabile che ha subito un cambiamento è y.

La spiegazione è ora chiara: le variabili x e z non sono state cambiate semplicemente perchè i loro valori sono stati copiati in a e b, e i cambiamenti sono stati fatti sulle zone di memoria che corrispondono alle nuove variabili a e b. Il valore di y è invece diverso. Questo avviene perchè la funzione esempio conosce l'indirizzo in cui è memorizzata, perchè è quello che sta scritto in b (mentre per esempio di x conosce solo il valore).

Se l'indirizzo di una variabile è noto, è possibile cambiare il suo valore semplicemente con una istruzione *indirizzo=nuovo_valore;. Questo è esattamente quello che si è fatto con la istruzione *b=3: si mette il valore 3 nella memoria puntata da b. Dato che b contiene l'indirizzo di y, questa istruzione mette 3 nella variabile y.

Si noti che l'istruzione *b=3 non altera il valore di b, ma solo quello della memoria puntata da b. Le modifiche che vengono poi fatte a b (ossia b=NULL;) sono modifiche a una variabile locale di esempio, per cui non vengono viste dal programma principale.

Volendo riassumere, diamo la regola generale sul passaggio dei parameteri:

  1. quando si passa una variabile, il suo valore viene copiato, per cui le modifiche fatte all'interno della procedura non hanno effetto sulle variabili del programma principale;
  2. quando si passa un indirizzo, la funzione è in grado di modificare il contenuto della zona di memoria che parte da quell'indirizzo; le modifiche sono quindi modifiche a zone di memoria in cui ci sono variabili del programma principale.

Volendo essere ancora più pratici, la regola è che se si passa una variabile normalmente allora le modifiche fatte nella funzione non hanno effetto; se si passa un indirizzo con &variabile e poi si usa * nella funzione, le modifiche sono viste dal programma principale. Questa regola si ottiene semplicemente applicando la regola generale di creazione di nuove variabili nelle funzione e il significato dei puntatori. È sempre bene però ricordare il perchè le cose funzionano in questo modo, altrimenti situazioni più complesse (per esempio, puntatori a puntatori) potrebbero risultare incomprensibili.