Indirizzi e Puntatori

 


 

Operatore di indirizzo &

 

L'operatore unario di indirizzo :

&

restituisce l'indirizzo della locazione di memoria dell'operando.

L'operando deve essere un ammissibile l-value. Il valore restituito dall'operatore non può essere usato come l-value (in quanto l'indirizzo di memoria di una variabile non può essere assegnato in un'istruzione, ma è predeterminato dal programma).

Esempi (notare l'uso delle parentesi per alterare l'ordine delle precedenze):
      &a      ammesso, purché a sia un l-value
&(a+1) non ammesso, in quanto a+1 non é un l-value
&(a>b?a:b) ammesso, in quanto l'operatore condizionale può restituire un l-value,
purché a e b siano l-values
&a = b non ammesso, in quanto l'operatore & non può restituire un l-value

 

Gli indirizzi di memoria sono rappresentati da numeri interi, in byte, e, nelle operazioni di output, sono scritti, di default, in forma esadecimale.

[p26]

 


 

Cosa sono i puntatori ?

 

I puntatori sono particolari tipi del linguaggio. Una variabile di tipo puntatore é designata a contenere l'indirizzo di memoria di un'altra variabile (detta variabile puntata), la quale a sua volta può essere di qualunque tipo, anche non nativo (persino un altro puntatore!).

 


 

Dichiarazione di una variabile di tipo puntatore

 

Benché gli indirizzi siano numeri interi e quindi una variabile puntatore possa contenere solo valori interi, tuttavia il C++ (come il C) pretende che nella dichiarazione di un puntatore sia specificato anche il tipo della variabile puntata (in altre parole un dato puntatore può puntare solo a un determinato tipo di variabili, quello specificato nella dichiarazione).

Per ottenere ciò, bisogna usare l'operatore di dichiarazione :  *
Es. :     int * pointer      

dichiara  (e definisce) la variabile pointer, puntatore a variabile di tipo int

Nota: nelle definizioni multiple * va ripetuto: in altre parole, l'operatore di dichiarazione * va considerato, dal punto di vista sintattico, un prefisso dell'identificatore e non un suffisso del tipo.

Si può dire pertanto che, a questo punto della nostra conoscenza, il numero dei tipi del C++ é "raddoppiato": esistono tanti tipi di puntatori quanti sono i tipi delle variabili puntate.

Un puntatore accetta quasi sempre il casting, purché il risultato della conversione sia ancora un puntatore. Tornando all'esempio precedente, l'operazione di casting:
             (double
*)pointer
restituisce un puntatore a una variabile di tipo double.
Nota2: nel casting, invece, l'operatore di dichiarazione * è un suffisso del tipo. (!)

Si può anche dichiarare un puntatore a puntatore.
Es. :            double** pointer_to_pointer
  dichiara  (e definisce) la variabile pointer_to_pointer,
puntatore a puntatore a variabile di tipo double     

 


 

Assegnazione di un valore a un puntatore

 

Sappiamo che gli indirizzi di memoria non possono essere assegnati da istruzioni di programma, ma sono determinati automaticamente in fase di esecuzione; quindi non si possono assegnare valori a un puntatore, salvo che in questi quattro casi:

Quanto detto per le assegnazioni vale anche per le inizializzazioni.

Va precisato, comunque, che ogni tentativo di assegnare valori a un puntatore in casi diversi da quelli sopraelencati  (per esempio  l'assegnazione di una costante) costituisce un errore che non viene segnalato dal compilatore, ma che può produrre effetti indesiderabili (o talvolta disastrosi) in fase di esecuzione.

 


 

Aritmetica dei puntatori

 

Abbiamo detto che il valore assunto da un puntatore é un numero intero che rappresenta, in byte, un indirizzo di memoria. Il C++ (come il C) ammette le operazioni di somma fra un puntatore e un valore intero (con risultato puntatore), oppure di sottrazione fra due puntatori (con risultato intero). Tali operazioni vengono però eseguite in modo "intelligente", cioè tenendo conto del tipo della variabile puntata. Per esempio, se si incrementa un puntatore a float di 3 unità, in realtà il suo valore viene incrementato di 12 byte.

Queste regole dell'aritmetica dei puntatori assicurano che il risultato sia sempre corretto, qualsiasi sia la lunghezza in byte della variabile puntata. Per esempio, se p punta a un elemento di un array, p++ punterà all'elemento successivo, qualunque sia il tipo (anche non nativo) dell'array.

[p27]

 


 

Operatore di dereferenziazione *

 

L'operatore unario di dereferenziazione * (che abbrevieremo in deref.) di un puntatore restituisce il valore della variabile puntata dall'operando ed ha un duplice significato:

In pratica l'operazione di deref. é inversa a quella di indirizzo. Infatti, se assegniamo a un puntatore p l'indirizzo di una variabile a,
          p = &a ;
allora la relazione logica:   
*p == a     risulta vera, cioè la deref. di p coincide con a.

Ovviamente non é detto il contrario, cioè, se assegniamo alla deref. di p il valore di a,
          p = &b ;
         
*p = a ;
ciò non comporta automaticamente che in p si ritrovi l'indirizzo di a (dove invece resta l'indirizzo di b), ma semplicemente che il valore della variabile puntata da p (cioè b) coinciderà con a.

 


 

Puntatori a void

 

Contrariamente all'apparenza un puntatore dichiarato a void,
es.:                                            void* vptr;
può puntare a qualsiasi tipo di variabile. Ne consegue che a un puntatore a void si può assegnare il valore di qualunque puntatore, ma non viceversa (é necessario operare il casting).
Es.:     definiti: int* iptr;    e     void* vptr;
é ammessa l'assegnazione:     vptr = iptr;
ma non: iptr = vptr;
bensì: iptr = (int*)vptr;

 
I puntatori a void non possono essere dereferenziati nè possono essere inseriti in operazioni di aritmetica dei puntatori. In generale si usano quando il tipo della variabile puntata non è ancora stabilito al momento della definizione del puntatore, ma è determinato successivamente, in base al flusso di esecuzione del programma.

 


 

Errori di dangling references

 

In C++ (come in C) l'assegnazione dell'indirizzo di una variabile a a un puntatore p :
                 p = &a ;
e il successivo accesso ad a tramite deref. di p, possono portare a errori di dangling references (perdita degli agganci) se puntatore e variabile puntata non condividono lo stesso ambito d'azione. Infatti, se l'ambito di p é più esteso di quello di a (per esempio se p é una variabile globale) e a va out of scope mentre p continua ad essere visibile, la deref. di p accede ad un'area della memoria non più allocata al programma, con risultati spesso imprevedibili.

[p28]

 


 

Funzioni con argomenti puntatori

 

Quando, nella chiamata di una funzione, si passa come argomento un indirizzo (sia che si tratti di una variabile puntatore oppure del risultato di un'operazione di indirizzo), per esempio (essendo, al solito, p un puntatore e a una qualsiasi variabile):

funz(.... p ....)     oppure       funz(.... &a ....)

nella definizione (e ovviamente anche nella dichiarazione) della funzione il corrispondente argomento va dichiarato come puntatore; continuando l'esempio (se a é di tipo int):
                void funz(.... int
* p ....)

L'argomento é, come sempre, passato by value. In C++ é anche possibile, passarlo by reference, nel qual caso bisogna indicare entrambi gli operatori di dichiarazione *  e  & :
                void funz(.... int
*& p ....)

Se il puntatore é passato by value, nella funzione viene creata una copia del puntatore e, qualsiasi modifica venga fatta al suo valore, il corrispondente valore nel programma chiamante rimane inalterato. In questo caso, tuttavia, tramite l'operazione di deref., la variabile puntata (che si trova nel programma chiamante), é accessibile e modificabile dall'interno della funzione.

Es.:       programma chiamante:   int a = 10; ...... funz(&a);
funzione: void funz( int* p) { ....*p = *p+5; .... }

alla fine, nella variabile a si trova il valore 15 (in questo caso non esistono problemi di scope, in quanto la variabile a, pur non essendo direttamente visibile dalla funzione, é ancora in vita e quindi é accessibile tramite un'operazione di deref.).

Per i motivi suddetti, quando l'argomento della chiamata é un indirizzo, si dice impropriamente che la variabile puntata é trasmessa by address e che, per questa ragione, é modificabile. In realtà l'argomento non é la variabile puntata, ma il puntatore, e questo é trasmesso, come ogni altra variabile, by value.

[p29]

 


 

Torna all'Indice