Puntatori NULL in linguaggio C

In linguaggio C, un puntatore non inizializzato contiene un indirizzo di memoria casuale, che potrebbe essere valido o meno. Per indicare che un puntatore non punta ad alcuna locazione di memoria, possiamo assegnargli il valore speciale NULL. In questa lezione vedremo come utilizzare il puntatore NULL e come verificare se un puntatore è valido o meno.

Puntatori non inizializzati

Abbiamo visto, nelle lezioni precedenti, che quando dichiariamo ma non inizializziamo un puntatore in linguaggio C, esso punta ad una locazione di memoria qualunque.

int *p;

In questo caso, il puntatore p punta ad una locazione di memoria qualunque, che potrebbe essere una locazione di memoria valida o meno. Se proviamo ad accedere a quella locazione di memoria, potremmo ottenere un errore di segmentazione, oppure potremmo ottenere un valore casuale.

int *p;
*p = 5; // Errore di segmentazione

Il punto è che l'indirizzo di memoria a cui il puntatore p punta è casuale.

A prima vista, questo comportamento potrebbe sembrare strano, se non assurdo. La realtà è che lo standard del linguaggio C non impone che un puntatore, così come qualunque altro tipo di variabile, venga inizializzato con un valore noto, in fase di dichiarazione.

Lo stesso accade, per esempio, con le variabili intere:

int x;
printf("%d\n", x); // Valore casuale

In questo caso, la variabile x non è stata inizializzata, e il valore che otteniamo è casuale. Non necessariamente essa contiene il valore 0.

Ma perché il valore è casuale?

La risposta è che, quando il programma si avvia, il sistema operativo assegna un blocco di memoria al programma, e il compilatore C non aggiunge codice per ripulire la memoria assegnata. Quindi, il valore di una variabile non inizializzata è il valore che si trovava già in quella locazione di memoria. In altre parole, la memoria potrebbe essere già stata sporcata da un altro programma precedentemente in esecuzione.

Altri linguaggi di programmazione provvedono in automatico ad aggiungere, senza che il programmatore lo richieda, routine di codice che, in fase di avvio, inizializzano le variabili con valori noti.

In C questo non accade ed è responsabilità del programmatore provvedere all'inizializzazione delle variabili.

Puntatori non validi: NULL

La conseguenza di quanto detto sopra è che un puntatore non inizializzato contiene comunque un indirizzo di memoria che, in quanto casuale, potrebbe puntare ad una locazione di memoria valida o meno.

In un certo senso, quindi, non esiste un puntatore non valido in senso stretto. Ossia un puntatore che non punti a nulla.

Ad esempio, se eseguiamo il seguente codice:

int *p;
if (p) {
    /* ... */
}

La condizione all'interno dell'istruzione if risulterà sorprendentemente vera! Anche se il puntatore p non è stato inizializzato. Questo perché l'indirizzo al suo interno sarà, nella maggior parte dei casi, diverso da zero e per l'istruzione if ciò equivale a true.

Come possiamo, allora, distinguere se un puntatore è valido o meno, ossia se punti a qualcosa oppure a nulla? Possiamo assegnare un valore non valido ad un puntatore per indicare che esso non punta ad alcuna locazione di memoria?

La risposta è si. In C, esiste un valore speciale che possiamo assegnare ad un puntatore per indicare che esso non punta ad alcuna locazione di memoria. Questo valore è NULL.

int *p = NULL;

if (p == NULL) {
    printf("Il puntatore p non punta ad alcuna locazione di memoria\n");
}

Il valore NULL va inteso come un valore speciale che indica una locazione di memoria inesistente.

Si tratta, a tutti gli effetti, di una macro che si traduce in un valore numerico, che in genere è 0.

La macro NULL è definita in vari file di intestazione della libreria standard del C, alcuni dei quali sono:

  • stdio.h
  • stdlib.h
  • string.h
  • time.h
  • stddef.h

Quindi, per utilizzare la macro NULL, è necessario includere almeno uno di essi.

Definizione

Puntatore NULL

La macro NULL è un valore speciale che può essere assegnato ad un puntatore per indicare che esso non punta ad alcuna locazione di memoria.

Test di Validità di un Puntatore

Molte funzioni, anche della libreria standard, restituiscono puntatori.

Potrebbero verificarsi casi in cui la funzione invocata non possa restituire un puntatore valido. Questo comportamento lo approfondiremo nelle prossime lezioni. Molto spesso, in questi casi, le funzioni di libreria restituiscono NULL proprio per indicare che non è stato possibile restituire un puntatore valido.

Pertanto, è sempre buona prassi di programmazione, ogni qualvolta si riceva un puntatore da una funzione, verificare se esso sia valido o meno.

Per farlo, in linguaggio C, esistono vari modi, ma quello migliore è il seguente:

int *p = funzione();

if (p == NULL) {
    printf("Errore: la funzione non ha restituito un puntatore valido\n");
}
else {
    /* ... */
}

In questo modo si confronta in maniera esplicita il puntatore restituito con il valore NULL.

In molti programmi, però, spesso si trovano confronti di questo tipo:

int *p = funzione();

if (!p) {
    printf("Errore: la funzione non ha restituito un puntatore valido\n");
}
else {
    /* ... */
}

Dal momento che NULL si traduce nella maggior parte dei sistemi con il valore 0, il confronto if (!p) è equivalente a if (p == NULL).

Addirittura, in certi programmi si effettua il confronto diretto con 0:

int *p = funzione();

if (p == 0) {
    printf("Errore: la funzione non ha restituito un puntatore valido\n");
}
else {
    /* ... */
}

Queste due ultime forme di confronto, però, sono da evitare, in quanto meno esplicite e meno leggibili.

Consiglio

Verifica di un Puntatore

Quando si vuole verificare se un puntatore è valido o meno, è buona prassi confrontarlo con il valore NULL:

if (p == NULL) {
    /* ... */
}

In Sintesi

In questa lezione abbiamo introdotto il puntatore NULL:

  • Abbiamo visto che un puntatore non inizializzato contiene un indirizzo di memoria casuale, che potrebbe essere valido o meno.
  • Abbiamo introdotto il valore speciale NULL, che indica che un puntatore non punta ad alcuna locazione di memoria.
  • Abbiamo visto come verificare se un puntatore è valido o meno.

Nella prossima lezione studieremo un altro importante tipo di puntatore: il puntatore void.