Ed eccoci arrivati all’argomento che per tanti utenti rappresenta la “bestia nera” della programmazione in C, i puntatori. Purtroppo devo darvi una brutta notizia, tutta la programmazione moderna si basa in maniera più o meno velata sull’uso dei puntatori, quindi non fatevi venire in mente di saltare questa lezione e mettiamoci subito al lavoro.
Cos’è un puntatore?
Un puntatore non è nient’altro che un tipo di variabile così come lo sono int, long, char etc, il suo scopo, però, non è quello di memorizzare l’informazione in sè, come accade per le classiche variabili, ma quello di memorizzare l’indirizzo di memoria dove di fatto l’informazione è memorizzata.
Nota: Per chi non lo sapesse la memoria dei nostri computer è organizzata in blocchi, ciascun blocco ha un proprio indirizzo (che non è altro che un numero, spesso espresso in esadecimale come ad esempio 0x345F2A).

Va precisato che anche il puntatore, in quanto variabile, è archiviato in memoria, quindi ha un suo proprio indirizzo, ma questo non ci deve confondere le idee.
Nell’immagine vediamo 4 variabili, l’ultima memorizzata all’indirizzo di memoria 0x803FA4 è una variabile di tipo puntatore ed il suo contenuto è 0x803FA2, quando dal programma accederemo a questa variabile (vedremo a breve come) verremo “dirottati” all’allocazione di memoria 0x803FA2 che ha come contenuto il numero 42.
Utilizzare una variabile che contenga l’indirizzo dove recuperare il dato è utile per realizzare quello che viene chiamato indirizzamento indiretto, ovvero piuttosto che utilizzare direttamente la variabile x utilizzo la variabile presente all’indirizzo di memoria puntato da x.
Anche se può sembrare piuttosto esoterico, l’indirizzamento indiretto è indispensabile per il funzionamento interno della CPU (link) mentre i linguaggi di più alto livello lo utilizzano per fornire maggiore flessibilità ai programmi e per velocizzare le operazioni con variabili molto grandi in memoria.
Facciamo un piccolo esempio, supponiamo di avere una variabile che occupi veramente molto spazio, e che questa variabile debba essere usata in due punti molto diversi del nostro programma. Piuttosto che effettuare una copia del dato, che comporterebbe uno spreco di tempo e memoria, possiamo creare un puntatore all’allocazione e utilizzare il puntatore all’interno del nostro codice così che quando ci servirà una copia del dato basterà effettuare una copia del solo puntatore, risparmiando un mucchio di tempo.
Suppongo che nessuno si stia ponendo in questo momento una domanda cruciale, cioè: “Se il puntatore contiene solo l’informazione sull’indirizzo dove reperire il dato, dov’è l’informazione sul tipo di dato?” ovvero “Come fa il nostro programma a sapere quanti byte leggere a partire dall’indirizzo di memoria puntato dal puntatore? Per un char dovrebbe leggere solo un byte, per un int 4 byte etc etc..” Bene, ottima domanda! Proprio per questo tutti i puntatori hanno un tipo, dichiareremo infatti un puntatore ad intero, oppure un puntatore a char e così via, in questo modo il nostro programma saprà quanti byte leggere.
Una piccola eccezione è data dal tipo “puntatore a void”, il termine “void” l’abbiamo già incontrato quando abbiamo parlato delle funzioni, dicendo che una funzione che non restituisce alcun valore si dichiara come
void nomefunzione {
//codice della funzione.
}
In questo caso il termine “void” indica che si tratta di un puntatore generico, che non ha specificato nessun tipo di dato, quindi punta in modo generico ad un intero blocco della memoria. Per utilizzarlo è sempre possibile utilizzare l’operazione di cast (lezione 7).
Dichiarazione di un puntatore
Per dichiarare una variabile di tipo puntatore si usa una sintassi molto simile alla dichiarazione di una variabile normale, l’unica differenza è che bisogna inserire un ” * ” prima del nome della variabile. Per esempio per dichiarare due variabili di tipo rispettivamente puntatore ad intero e puntatore a carattere la sintassi da utilizzare è la seguente
int *a;
char *b;
Una piccola nota sul carattere ” * “: non è necessario scriverlo attaccato al nome della variabile, questa è la convenzione che utilizzo io, ma in altri casi potreste trovare qualcosa come
int* a;
char* b;
La questione è puramente simbolica, ma anche qui ci sono parecchie scuole di pensiero, se avete voglia potete leggere il parere di B. Stroustrup, l’inventore del C++ (link). L’ unica cosa certa è che se vi capita di parlare con un programmatore non dovete dire “int asterisco a” ma “int star a” se volete essere sicuri di essere capiti.
L’operatore (perché si tratta di un operatore, non di un orpello grafico) ” * ” si chiama operatore di deferenziazione e quando è anteposto al nome di una variabile fa si che al suo posto venga utilizzato il valore dell’allocazione di memoria all’indirizzo contenuto nella variabile.
Vediamo se tutto è chiaro con una domanda difficile.
Supponiamo di aver scritto questo codice, in cui dichiariamo una variabile di tipo puntatore ad intero e poi assegnamo tramite l’operatore di deferenziazione il valore 42.
#include
int main(int argc, char **argv) {
int i,j; //variabili dichiarate ma non inizializzate. serviranno dopo.
int *a;
*a = 42;
printf("Il puntatore punta ad una variabile che vale %d ", *a);
}
Tutto sembra corretto, anche se compiliamo non ci sono errori particolari, eppure quando eseguiamo il programma appare un drammatico “segmentation fault”, come mai?
Esaminiamo il codice, in particolare la riga:
*a = 42;
Questa istruzione dovrebbe assegnare 42 alla variabile contenuta all’indirizzo di memoria contenuto nella variabile a, ma purtroppo la variabile a non è stata inizializzata, quindi il suo valore, di solito è zero oppure un valore non prevedibile, quindi quello che accade è che il programma tenta di scrivere “42” in zone di memoria che non gli appartengono, quindi il sistema operativo per sicurezza lo blocca e genera l’errore “segmentation fault” facendo crashare il programma.

Quindi abbiamo scoperto che quando si usano i puntatori in realtà le variabili da inizializzare sono due e non più una sola, dobbiamo inizializzare il contenuto della variabile puntatore e inizializzare il contenuto della variabile puntata. Ma come si inizializza una variabile di tipo puntatore? Ci sono due metodi: il primo, che vedremo in una prossima lezione, è quello di chiedere direttamente al sistema operativo un indirizzo di memoria libero da poter utilizzare, mentre il secondo consiste nell’assegnare ad un puntatore l’indirizzo di una variabile già dichiarata. Ad esempio potremmo assegnare al puntatore “a” il valore 0x803FA3 che è l’indirizzo dove è memorizzata la variabile j.
In questo modo con queste due inizializzazioni:
a = 0x803FA3;
*a = 42;
non si genererebbe errore, perché 0x803FA3 è un indirizzo appartenente al nostro programma ed il sistema operativo non ci impedisce di accedervi.
Attenzione: Nella riga a = 0x803FA3; non ho dimenticato l’asterisco e non è un refuso di stampa, in questo caso vogliamo modificare il valore contenuto all’interno del puntatore, e non della variabile puntata dal puntatore, quindi il simbolo ” * ” non va messo.
Ma come facciamo a conoscere l’indirizzo di memoria che il sistema operativo ha assegnato alle nostre variabili? 0x803FA3 è solo un esempio inventato e non c’è modo di sapere durante la scrittura di un codice qual’è l’indirizzo che verrà assegnato alle nostre variabili durante l’esecuzione del programma. Quindi come fare? Possiamo utilizzare un operatore che è il duale dell’operatore di deferenziazione ed è l’operatore ” & “.
Anteposto al nome della variabile fa si che al suo posto venga utilizzato l’indirizzo della variabile.
Quindi potremmo riscrivere il codice precedente in questo modo:
#include
int main(int argc, char **argv) {
int i,j; //variabili dichiarate ma non inizializzate. serviranno dopo.
int *a;
a = &j; //assegno al puntatore a l'indirizzo della variabile j
*a = 42; //assegno all'indirizzo puntato da a il valore 42
printf("Il puntatore punta ad una variabile che vale %d ", *a);
}
Quello che abbiamo fatto in questo esempio è quello di far puntare il puntatore a all’indirizzo dov’è memorizzata la variabile j, quindi possiamo modificare il valore di j sia modificando j stessa sia modificando *a.
Vediamo in questo esempio qualche esempio di utilizzo dell’operatore * ed &:
#include
int main(int argc, char **argv) {
int valore_a;
int *ptr1;
ptr1 = &valore_a;
*ptr1 = 42;
printf("valore_a vale: %d \n", valore_a);
printf("*ptr1 vale: %d\n ", *ptr1);
//praticamente *ptr1 e valore_a sono sinonimi.
printf("La variabile valore_a e' memorizzata in %#x \n", &valore_a);
printf("La variabile ptr1 vale (punta a) %#x \n", ptr1);
printf("La variabile ptr1 e' memorizzata in %#x \n", &ptr1);
// non confondiamo l'indirizzo al quale è memorizzato il puntatore con //l'indirizzo al quale punta, queste righe evidenziano la differenza.
}
Copiate e compilate questo codice, studiate i commenti e parliamone sul forum.
Concludo questa importante lezione proponendo qualche esercizio:
- Scrivere un programma C che dopo aver dichiarato 3 interi e tre puntatori ad intero gli assegni dei valori arbitrari agli interi e poi calcoli il massimo utilizzando i puntatori e non le variabili intere.
- Che cosa succede se scrivo:
int *ptr1; ptr1 = &ptr1;
- Dati queste dichiarazioni:
int *p; int i; int k; i = 42; k = i; p = &i;
se volessi portare il valore di i a 75 quale operazione dovrei eseguire?
k = 75; *k = 75; p = 75; *p = 75; due o più delle precedenti risposte. - Le seguenti dichiarazioni sono corrette? Fornire spiegazioni adeguate:
char c = 'A'; double *p = &c;
Credo che per questa lezione sia abbastanza 🙂
Alla prossima!
Letture consigliate:
Il linguaggio C. Principi di programmazione e manuale di riferimento (Accademica)
Brian W. Kernighan – Dennis M. Ritchie
Editore: Pearson | Lingua: Italiano | Brossura: 313 pagine
Prezzo Listino: EUR 27,00
Prezzo Promozione: EUR 22,95 con Spedizione gratuita
C. Corso completo di programmazione
Paul J. Deitel – Harvey M. Deitel
Editore: Apogeo | Lingua: Italiano | Brossura: 640 pagine
Prezzo Listino: EUR 39,00
Prezzo Promozione: EUR 33,15 con Spedizione gratuita










6 Responses to “12. I Puntatori – parte 1”
4 Maggio 2011
andrea90rmed eccoli..loro cosi odiosi [per me] 😀 all’università mi hanno creato problemi enormi…se capiso di piu con te ti farò una statua LoL
4 Maggio 2011
tommasociao!! bella questa lezione!! avrei dei dubbi su quella precedente però! come faccio a fare le domande nel forum? in che sezione? dove trovo le discussione su questo corso? (cercate di essere il più chiari possibile perchè sono un novello) grazie mille!! sempre i migliori!
4 Maggio 2011
andrea90rmvai nel forum,ti registri se non l’hai fatto,e scorrendo il forum trovi una sezione che si chiama come questo corso…entri e scrivi la
6 Maggio 2011
Corso di programmazione in C - I Puntatori - Array, matrici e aritmetica dei puntatori - parte 2 | devAPP[…] nostro corso completo di programmazione in C approfondiremo lo studio dei puntatori iniziato nella precedente lezione. Oggi esamineremo l’utilizzo dei puntatori come parametro per le funzioni e come base per la […]
21 Ottobre 2011
cazzascusate dovo posso trovare le soluzioni degli esercizi?
grazie
22 Ottobre 2011
Ignaziocsul forum c’è una sezione specifica per questo corso, prova a scrivere lì le tue considerazioni e vediamo se hai indovinato.
se cerchi nei vecchi post ricordo di aver già corretto qualcuno quindi trovi le soluzioni.
ciao