Barninga Z
a- a+

Gli operatori * e &

Gli operatori * e & 

Il C consente di pasticciare a volontà, ed anche...troppo, con gli indirizzi di memoria mediante particolaristrumenti, detti puntatori, o pointers

Un puntatore non è altro che una normalissimavariabile contenente un indirizzo di memoria. I puntatori nonrappresentano un tipo di dato in sé, ma piuttosto sonotipizzati in base al tipo di dato a cui... puntano,cioè di cui esprimono l'indirizzo. Perciòessi sono dichiarati in modo del tutto analogo ad unavariabile di quel tipo, anteponendo però al nome delpuntatore stesso l'operatore "*" ,detto operatore di indirezione (dereferenceoperator). 

Così, la riga 

int unIntero;

dichiara una variabile di tipo int avente nomeunIntero, mentre la riga 

int *puntaIntero;

dichiara un puntatore a int avente nomepuntaIntero (il puntatore ha nomepuntaIntero, non l'int... ovvio!).E' importante sottolineare che si tratta di un puntatorea integer: il compilatore C effettua alcune operazioni suipuntatori in modo automaticamente differenziato a seconda deltipo che il puntatore  indirizza[8], ma èaltrettanto importante non dimenticare mai che un puntatorecontiene semplicemente un indirizzo (o meglio un valore cheviene gestito dal compilatore come un indirizzo). Essoindirizza, in altre parole, un certo byte nella RAM; ladichiarazione del tipo "puntato" permette alcompilatore di "capire" di quanti byte si componel'area che inizia a quell'indirizzo e come èorganizzata al proprio interno, cioè quale significatoattribuire ai singoli bit. 

Si possono dichiarare più puntatori in un'unicariga logica, come del resto avviene per le variabili: la rigaseguente dichiare tre puntatori ad intero. 

int *ptrA, *ptrB, *ptrC;

Si noti che l'asterisco, o meglio, l'operatore diindirezione, è ripetuto davanti al nome di ognipuntatore. Se non lo fosse, tutti i puntatori dichiaratisenza di esso sarebbero in realtà... normalissimevariabili di tipo int. Ad esempio, la riga che seguedichiara due puntatori ad intero, una variabile intera, e poiancora un puntatore ad intero. 

int *ptrA, *ptrB, unIntero, *intPtr;

Come si vede, la dichiarazione mista di puntatori e variabiliè un costrutto sintatticamente valido; occorre, comeal solito, prestare attenzione a ciò che si scrive sesi vogliono evitare errori logici piuttosto insidiosi. Dettotra noi, principianti e distratti sono i più propensia dichiarare correttamente il primo puntatore e privare tuttigli altri dell'asterisco nella convinzione che il tipodichiarato sia int*. In realtà, una riga dicodice come quella appena riportata dichiara una serie dioggetti di tipo int; è la presenza ol'assenza dell'operatore di indirezione a stabilire,singolarmente per ciascuno di essi, se si tratti di unavariabile o di un puntatore. 

Mediante l'operatore & (detto"indirizzo di" , o address of) èpossibile, inoltre, conoscere l'indirizzo di unavariabile: 

float numero;     // dichiara una variabile float    float *numPtr;    // dichiara un puntatore ad una variabile float    numero = 12.5;    // assegna un valore alla variabile    numPtr = № // assegna al puntatore l'indirizzo della variabile

E' chiaro il rapporto tra puntatori e variabili? Unavariabile contiene un valore del tipo della dichiarazione,mentre un puntatore contiene l'indirizzo, cioè laposizione in memoria, di una variabile che a sua voltacontiene un dato del tipo della dichiarazione. Dopo leoperazioni dell'esempio appena visto, numPtr noncontiene 12.5, ma l'indirizzo di memoria alquale 12.5 si trova. 

Anche un puntatore è una variabile, ma contiene unvalore che non rappresenta un dato di un particolare tipo,bensì un indirizzo. Anche un puntatore ha il suo bravoindirizzo, ovviamente. Riferendosi ancora all'esempioprecedente, l'indirizzo di numPtr puòessere conosciuto con l'espressione &numPtre risulta sicuramente diverso da quello di numero,cioè dal valore contenuto in numPtr. Sembradi giocare a rimpiattino... 

Proviamo a confrontare le due dichiarazionidell'esempio: 

float numero;    float *numPtr;

Esse sono fortemente analoghe; del resto abbiamo appena dettoche la dichiarazione di un puntatore è identica aquella di una comune variabile, ad eccezionedell'asterisco che precede il nome del puntatore stesso.Sappiamo inoltre che il nome attribuito alla variabileidentifica un'area di memoria che contiene un valore deltipo dichiarato: ad esso si accede mediante il nome stessodella variabile, cioè il simbolo che, nelladichiarazione, si trova a destra della parola chiave cheindica il tipo, come si vede chiaramente nell'esempio chesegue. 

 

printf("%f " ,numero);

L'accesso al valore della variabile avviene nellamodalità appena descritta non solo in lettura, maanche in scrittura: 

numero = 12.5;

Cosa troviamo a destra dell'identificativo di tipo in unadichiarazione di puntatore? Il nome precedutodall'asterisco. Ma allora anche il nome del puntatore conl'asterisco rappresenta un valore del tipo dichiarato...Provate ad immaginare cosa avviene se scriviamo: 

printf("%f " ,*numPtr);

La risposta è: printf() stampa il valore di numero[9]. In altre parole, l'operatore diindirezione non solo differenzia la dichiarazione di unpuntatore da quella di una variabile, ma consente anche diaccedere al contenuto della variabile (o, più ingenerale, della locazione di memoria) indirizzata dalpuntatore. Forse è opportuno, a questo punto,riassumere il tutto con qualche altro esempio. 

float numero = 12.5;    float *numPtr = №

Sin qui  nulla di nuovo[10]. Supponiamo ora chel'indirizzo di numero sia, in esadecimale,FFE6 e che quello di numPtr siaFFE4: non ci resta che giocherellare un po' congli operatori address of ("&") edereference ("*")... 

printf("numero = %f " ,numero);    printf("numero = %f " ,*numPtr);    printf("l'indirizzo di numero e' %X " ,&numero);    printf("l'indirizzo di numero e' %X " ,numPtr);    printf("l'indirizzo di numPtr e' %X " ,&numPtr);

L'output prodotto è il seguente: 

numero = 12.5numero = 12.5l'indirizzo di numero è FFE6l'indirizzo di numero è FFE6l'indirizzo di numPtr è FFE4

Le differenza tra le varie modalità di accesso alcontenuto e all'indirizzo delle veriabili dovrebbe oraessere chiarita. Almeno, questa è la speranza. Tral'altro abbiamo imparato qualcosa di nuovo suprintf(): per stampare un intero in formatoesadecimale si deve inserire nella stringa, invece di%d, %X se si desidera che le cifreA­F siano visualizzate con caratteri maiuscoli,%x se si preferiscono i caratteriminuscoli. 

Va osservato che è prassi usuale esprimere gliindirizzi in notazione esadecimale. A prima vista puòrisultare un po' scomodo, ma, operando in tal modo, lalogica di alcune operazioni sugli indirizzi stessi (e suipuntatori) risulta sicuramente più chiara. Ad esempio,ogni cifra di un numero esadecimale rappresenta quattro bitin memoria: si è già visto come ciòpermetta di trasformare un indirizzo segmentato nel suoequivalente lineare con grande facilità. Per lacronaca, tale operazione è detta anche"normalizzazione" dell'indirizzo (o delpuntatore). 

Vogliamo complicarci un poco la vita? Eccovi alcuneinteressanti domandine, qualora non ve le foste ancoraposti... 

  1. Quale significato ha l'espressione *&numPtr
  2. Quale significato ha l'espressione **numPtr
  3. E l'espressione *numero
  4. E l'espressione &*numPtr
  5. &*numPtr e numPtr sono la stessa cosa? 
  6. Cosa restituisce l'espressione &&numero
  7. E l'espressione &&numPtr
  8. Cosa accade se si esegue *numPtr = 21.75
  9. Cosa accade se si esegue numPtr = 0x24A6
  10. E se si esegue &numPtr = 0xAF2B

 

Le soluzione alla fine della guida.



Ti potrebbe interessare anche

commenta la notizia