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;
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:
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;
/*
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 *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 "*":
- In una dichiarazione serve per dichiarare una variabile di tipo puntatore.
- 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:
void main() {
int i = 5;
int *p = &i;
printf("L'indirizzo di memoria %p contiene il valore %d\n", p, *p);
}
Risultato dell'esecuzione:
Esempio 2: Modifica valore mediate dereferenziazione
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:
Riflessioni
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:
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:
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.