Puntatori indefiniti

  1. i puntatori sono inizialmente indefiniti, come tutte la variabili
  2. cosa succede quando si usa un puntatore indefinito
  3. non si devono usare puntatori indefiniti

Negli esempi visti fino ad ora, i puntatori venivano usati soltanto per memorizzare indirizzi di variabili. In generale, un puntatore è una variabile che contiene un indirizzo di memoria.

Quando si definisce una variabile di tipo puntatore, per esempio con int *p;, si crea una variabile il cui contenuto è un indirizzo di memoria. Se non si assegna un valore a questa variabile, il suo contenuto è indefinito, quindi non è possibile sapere a priori quale indirizzo è scritto in p. Usare il valore di *p, oppure memorizzare un valore in *p produce un risultato indefinito, ossia non è possibile sapere a priori cosa succede (il risultato può cambiare di volta in volta). Questo avviene perchè il valore iniziale (indefinito) di p può essere l'indirizzo di una qualsiasi zona di memoria, che può essere o no associata a un'altra variabile del programma, e può anche essere una posizione di memoria a cui il programma non può accedere.

Per usare una variabile puntatore, è necessario che contenga l'indirizzo di una zona di memoria su cui siamo sicuri che:

  1. il sistema operativo ci permette di accedere;
  2. non viene modificata dal programma in modo inaspettato.

Consideriamo il programma nomalloc.c riportato qui sotto, che non segue queste regole.

int main() {
  int a;
  int *p;

  a=3;

  *p=12;

  printf("%d\n", a);

  return 0;
}

Il programma non rispetta la regola sul valore di un puntatore, che dovrebbe essere l'indirizzo di una zona di memoria in cui siamo certi di poter accedere senza problemi. Al contrario, il valore di p è indeterminato come tutte le variabili che non sono state inizializzate.

Consideriamo ora cosa può succedere quando si esegue questo programma.

La figura qui accanto mostra un possibile caso in cui il valore iniziale di p (su cui, come si è detto, non si può avere alcun controllo) è l'indirizzo di una zona di memoria a cui il programma non può accedere.

Quando si esegue l'istruzione *p=12, viene fatto un tentativo di mettere il valore 12 nella zona di memoria il cui indirizzo si trova in p. A seconda del tipo di sistema operativo che si sta usando, può venire generato un errore in esecuzione oppure si può produrre un blocco dell'intero sistema operativo (nel qual caso, il computer va riavviato).

La figura qui accanto mostra un altro possibile caso: quello in cui il valore contenuto in p casualmente risulta essere l'indirizzo di un'altra variabile del programma. Si tratta chiaramente di un caso molto poco probabile.

Quello che succede è che *p=12 mette il valore 12 nella zona di memoria della variabile a. Quando poi si va a stampare a, viene quindi stampato il valore 12.

Dal momento che la variabile a conteneva il valore 3, e il programma non esegue operazioni di assegnamento su a, ci si aspetterebbe la stampa del valore 3. La stampa di 12 è quindi un risultato non atteso.

Questo caso è simile al precedente, soltanto che la zona di memoria il cui indirizzo iniziale si trova in p si sovrappone soltanto parzialmente con la zona di memoria in cui è memorizzato a. In questo caso, viene stampato un valore che dipende da come i numeri sono rappresentati in memoria, ma tipicamente non è nè 12 è 3.

Da notare che tutte e tre le situazioni di sopra sono possibili, e non è nemmeno possibile prevedere a priori quale delle tre si verifica in una certa esecuzione. Il problema è quindi chiaro: i programmi che si scrivono devono comportarsi sempre nello stesso modo, e non fare ogni volta una cosa diversa.