::Il-mio.sito-web-dedicato-al.coding   

... un quaderno delgi appunti! di Giuseppe VACCA

Linguaggio di programmazione C


Puntatori e dereferenziazione

 

Punatatori - approfondimento

Una variabile di tipo puntatore può essere utilizzata solo per contenere l'indirizzo di memoria di un'altra variabile (variabile puntata) che può essere di qualunque tipo, persino un altro puntatore. Un puntatore può puntare solo ad un determinato tipo di variabile, quello specificato nella dichiarazione

int *Puntatore;

  • int: qualificatore di tipo
  • *: costruttore
  • Puntatore: nome della variabile puntatore

Si può anche dichiarare un puntatore a puntatore:

int **puntatore_a_puntatore;

#include <stdio.h>

void main() {

  int i = 6;
  int *p = &i;

  printf("indirizzo variabile intera 'i': %p\n", p);

  int **q = &p;

  printf("indirizzo della variabile puntatore 'p': %p\n", q);

}

 

Risultato dell'esecuzione:

 

Fig.1 - Puntatore a puntaore

 

L'oeratore unario di indirizzo '&' restituisce l'indirizzo della locazione di memoria dell'operando: si applica ad oggetti (variabili e costanti) che esistono in memoria, quindi già definiti.

Il puntatore è un tipo di dato scalare, che consente di rappresentare gli indirizzi delle variabili allocate in memoria. Il dominio di una variabile di tipo puntatore è un insieme di indirizzi: il valore di una variabile di tipo puntatore è l'indirizzo di un'altra variabile (variabile puntata).

Definizione di una variabile puntatore

In C i puntatori di definiscono mediate il costruttore '*':

<tipo_elemento_puntato> *<nome_puntatore>;

La definizione di una variabile puntatore riserva memoria solo per contenere l'indirizzo di un oggetto (variabile o costante), ma non riserva memoria per l'oggetto puntato. Il puntatore deve essere inizializzato o assegnato successivamente alla creazione della variabile puntata.

Inizializzazione e assegnazione

Il puntatore è sostanzialmente una variabile di tipo numerico pertatanto l'assegnazione avviene con l'operatore '=', come nell'esempio sotto: il numero da assegnare al puntatore deve essere rappresentativo di un indirizzo di memoria.

  • Definizione: tipo_puntato *nome_variabile_puntatore;
  • Definizione e inizializzazione: tipo_puntato *nome_variabile_puntatore = &nome_variabile_puntata;
  • Assegnazione: nome_variabile_puntatore = &nome_variabile_puntata;

 

int x = 6;
/*
 Viene creata una variabile di nome 'x'
 e le viene assegnato il valore 6
*/
int *p = &x
/*
 Viene viene definita p come variabile puntatore
 e viene inizializzata con l'indirizzo di 'x'
*/

 

int x = 6, y = 21;

int *p = &x; // Viene definita e inizializzata la variabile p con l'indirizzo di x
int *q = &y; // Viene definita e inizializzata la variabile q con l'indirizzo di y

p = q;
/*
 p e q diventano alias uno dell'altro
 i due puntatori puntano alla stessa variabile,
 alla variabile puntatore p viene assegnato
 l'indirizzo al quale punta la variabile puntatore q
*/

Uso

Per accedere all'oggetto puntato da un puntatore all'interno di espressioni si usa l'operatore '*' detto operatore di deriferimento o di indirezione.

  • p: puntaore
  • *p: oggetto punatato

La sintassi per indicare l'oggetto puntato e quello della definizione di puntatore sono identiche. Quindi è bene non confondere il significato degli asterischi (*):

  • nella definizione: <tipo> *p = &x; l'indirizzo prodotto da &x viene messo in p e non in *p
  • nell' uso: *p = 12; in questo caso l'asterisco è l'operatore di deriferimento e quindi *p identifica l'oggetto puntato da p (cioè 'x') ed è quindi x che viene modificata.

Quindi è importante sottolineare che non bisogna confondere le due occorrenze di "*":

  1. In una dichiarazione serve per dichiarare una variabile di tipo puntatore.
  2. In una espressione è l'operatore di dereferenziamento.

Dereferenziazione

Dereferenziazione è loperazione di accesso al valore archiviato in un indirizzo di memoria raggiungibile mediante un puntatore.

Dereferenziare un puntatore significa, quindi, accedere al valore memorizzato all'indirizzo di memoria indicato da quel puntatore. Ciò consente di lavorare con i dati effettivi e non solo con la posizione di memoria utilizzando l'operatore di dereferenziazione (*), è possibile recuperare e manipolare il valore puntato da un puntatore.

 

Esempio 1:

#include <stdio.h>

void main() {

  int i = 5;
  int *p = &i;

  printf("L'indirizzo di memoria %p contiene il valore %d\n", p, *p);
}

 

Risultato dell'esecuzione:

 

Fig.5 - Indirizzo e valore

 

Esempio 2: Modifica valore mediate dereferenziazione

#include <stdio.h>

void main() {

  int i = 5;

  printf("Valore delle variabile i: %d\n", i);

  int *p = &i;

  printf("L'indirizzo: %p associato alla variabile intera i contiene il valore %d\n", p, *p);

  *p = 8; // Viene modificato il conetuto dell'indirizzo di memoria della variabile i

  printf("L'indirizzo: %p associato alla variabile intera i contiene ora il valore %d\n", p, *p);
  printf("Valore delle variabile i: %d\n", i);
}

 

Risultato dell'esecuzione:

 

Fig.6 - Dereferenziazione

 

Riflessioni

int x = 8;

int *p;
p = &x;
/*
 Da questo momento, *p e x sono due modi alternativi per denotare la stessa variabile.
*/


Ancora sul casting

Partiamo dall'assunto che nella definizione di un puntatore è necessario indicare il tipo della variabile puntata questo implica che il compilatore effettua controlli statici sull'uso dei puntatori e ciò impica che:

  • un puntatore a int può contenere solo indirizzi di variabili di tipo int
  • un puntatore a float può contenere solo indirizzi di variabili di tipo float
  • un puntatore a struct frazione può contenere solo indirizzi di variabili di tipo struct frazione

Un utilizzo dei puntatori fuori da queste regole porterebbe a risultati imprevedibili come dimostra il seguente esempio:

#include <stdio.h>

void main() {

  int i;
  float f;

  i = 4;
  f = 3.5;

  int *pi = &i;
  float *pf = &f;

  printf("PRIMA DEL CASTING\n");
  printf("Valore nell'indirizzo della variabile puntatore pi: %d\n", *pi);
  printf("Valore nell'indirizzo della variabile puntatore pf: %f\n", *pf);
  printf("\n");

  printf("Indirizzo della variabile puntatore pi: %p\n", pi);
  printf("Indirizzo della variabile puntatore pf: %p\n", pf);
  printf("\n");
  /*
    L'istruzione pf = pi; genera un warning - infatti sto cercando di assegnare
    ad un puntatore che punta a float un puntatore che punta a int, in modo tale che
    entrabi i puntatori
    puntino allo stesso indirizzo che contiene un int.
    Posso ovviare al warning con la seguente istruzione:
  */

  pf = (float *)pi;
 // dove ho effettuato un casting della variabile puntatore: ho valutato
 // la variabile int come float
 // per poter assegnare il suo valore (indirizzo) alla variabile pf.
 // La compilazione non genera alun warning,
 // ed ancora la due variabili puntatore ora puntano allo stesso indirizzo di memoria
 // come e' dimostrato dalla stampa a video degli indirizzi puntati dalle due variabili

  printf("DOPO IL CASTING\n");
  printf("Indirizzo della variabile puntatore pi: %p\n", pi);
  printf("Indirizzo della variabile puntatore pf: %p\n", pf);
  printf("\n");
 // Accade l'imprevedibile se provo a stamapre il valore contenuto
 // nello stesso indirizzo di memoria
 // a cui le due variabili ora puntano entrambe

  printf("Valore nell'indirizzo della variabile puntata da pi: %d\n", *pi);
  printf("Valore nell'indirizzo della variabile puntata da pf: %f\n", *pf);
  /*
    Viene stampato correttamente solo il valore contenuto nell'indirizzo
    della variabile di tipo intero
    Dopo il casting non posso accedere correttamente mediate dereferenziamneto
    al valore contenuto nell'indirizzo
    della variabile f
  */
}

Risultato dell'esecuzione:

 

Fig.7 - Casting

 

Il cast di puntatori deve essere fatto con attenzione. Se il tipo di dato originale e il tipo di destinazione non sono compatibili, il risultato potrebbe essere inaspettato e potenzialmente dannoso. Ad esempio, se si tenta di interpretare una sequenza di bit come un numero in virgola mobile quando in realtà rappresenta un intero, il risultato sarà un valore casuale o errato.
È sempre consigliabile utilizzare il cast solo quando si ha la certezza che i dati puntati siano compatibili con il nuovo tipo di puntatore.
In sintesi, modificare il tipo di un puntatore in C è possibile attraverso il casting, ma è fondamentale comprendere le implicazioni e usare questa operazione con cautela.