13: Puntatori in C

È arrivato il momento di affrontare uno degli argomenti più spigolosi del C (e dei linguaggi di programmazione in generale): i puntatori.
Essi altro non sono che dei particolari tipi di variabili; tuttavia differiscono dai tipi di variabili “classici” per il fatto che i valori che andremo ad inserire al loro interno non hanno un significato a sé stante, che possa renderli comprensibili o direttamente modificabili dal programmatore, ma contengono degli indirizzi di specifiche aree di memoria in cui sono contenuti i dati su cui dobbiamo andare a lavorare, che sono delle informazioni che servono al calcolatore per eseguire i nostri programmi. In pratica la memoria del nostro computer è essenzialmente strutturata come una serie di celle, che sono intuitivamente rappresentabili come dei contenitori (come fossero delle scatole o dei cassetti uno adiacente all’altro) all’interno di quali possiamo inserire dei valori, codificati in bit (sequenze di 0 e 1) che possono rappresentare numeri interi o razionali, caratteri, ecc. Ad ogni cella è assegnato un indirizzo (le celle di fatto sono numerate) tramite il quale è possibile accedere al valore in essa contenuto per leggerlo o modificarlo. Ogni istruzione in C (linguaggio di alto livello) dovrà essere tradotta in una serie di istruzioni molto più semplici di un linguaggio macchina (di basso livello) eseguibili dal processore. Le istruzioni macchina più’ comuni sono quelle di lettura/scrittura di una cella di memoria, di spostamento di un dato da una cella ad un’altra o delle banali somme. In più il processore ha dei registri in cui può depositare momentaneamente dati e risultati delle operazioni che deve eseguire per ogni istruzione, e tramite delle apposite istruzioni macchina può trasferire informazioni da e verso la memoria. Insomma, evitando di dilungarci più del necessario possiamo modellare la memoria di un computer come un mega vettore di celle, una consecutiva all’altra, dove ad ogni cella è assegnato un indirizzo. Questi indirizzi,che in generale sono dei numeri interi, e sono nascosti all’utente finale, possono essere utilizzati direttamente da noi programmatori nei nostri programmi inserendoli in delle variabili di tipo puntatore.
Vediamo ora come dichiararli!

Andremo a dichiarare le variabili puntatori nella parte del programma di dichiarazione delle variabili globali insieme a tutte le altre, tuttavia la notazione che si usa è leggermente diversa rispetto alle variabili standard (in modo da non confonderle):

int *p;
int x=0, y=4;

int main()
{
//istruzioni
}

Abbiamo definito 2 variabili di tipo intero (x e y) inizializzate rispettivamente a 0 e 4; inoltre p è il nostro puntatore. Esso è definito come una variabile intera inserendo il simbolo “ * “ prima del nome della variabile.
P non è ancora inizializzato. Si tenga a mente che i puntatori sono essenzialmente dei numeri interi e di conseguenza sono anch’essi contenuti in delle celle di memoria dotate di un indirizzo.
Vediamo ora le principali operazioni a disposizione per i puntatori:

int *p;
int x=0, y=4;

int main()
{
p=&x;
printf(“l’indirizzo della cella di memoria che contiene x e’ %d”, p);
printf(“l’indirizzo della cella di memoria che contiene p e’ %d”, &p);
y=*p;

x=p;
*p=100;
}

Analizziamo con calma le singole istruzioni di assegnamento del main.
p=&x;
La prima istruzione assegna al puntatore p l’indirizzo della cella di memoria che contiene l’intero x. Si dice anche che p punta alla cella che contiene x. Graficamente, si possono rappresentare le 2 celle di memoria in questione come dei quadratini al cui interno scriviamo il valore contenuto nelle celle, numerati con i rispettivi indirizzi. In particolare, il valore contenuto nella cella dedicata a p è anch’esso un indirizzo, e in questo caso è proprio l’indirizzo della cella di x, che in generale sarà diverso dall’indirizzo della cella di p. Si può rappresentare questa relazione tra le due celle con una freccia che collega la cella di p con quella di x.
L’operatore ” & “ applicato ad una qualsiasi variabile, ne restituisce l’indirizzo della cella memoria in cui è contenuto il valore della variabile. Al contrario il simbolo “ * “ applicato ad una variabile di tipo indirizzo (o puntatore) restituisce il valore contenuto nella cella di memoria che corrisponde a quell’indirizzo. Si suppone nelle immagini sotto che gli indirizzi delle celle di x, y e p siano rispettivamente 10, 50 e 20 (sono semplicemente a titolo di esempio).

img1

y=*p;
La seconda istruzione assegna ad y il valore contenuto nella cella puntata dal puntatore p. Dato che nella precedente istruzione abbiamo imposto a p di puntare alla cella in cui era memorizzato il valore di x, a questo punto avremo y=x=0.

img 2

x=p;
Questa terza istruzione, a differenza della precedente a cui purtroppo assomiglia un po’, assegna alla variabile intera x il valore di p. Ma p è un puntatore, e quindi il suo valore è un indirizzo. Questo non rappresenta un problema in quanto abbiamo detto che gli indirizzi sono dei numeri interi, e x è anch’esso un intero. Quindi con questa istruzione assegneremo ad x l’indirizzo contenuto in p, che è lo stesso indirizzo della cella di memoria di x, in quanto p punta ad x. Quindi adesso la cella x conterrà il proprio indirizzo.

img 3

*p=100
Quest’ultima istruzione assegna alla variabile puntata da p (in questo caso x) il valore 100.
Quindi in conclusione avremo x=100 , y=0 e p=10 (indirizzo di x).

img 4

Mi rendo conto perfettamente che i puntatori sono un argomento un po’ ostico da digerire, ma la loro importanza va oltre la loro scarsa intuitività d’uso: i puntatori permettono ad un linguaggio come il C, in generale abbastanza rigido una flessibilità davvero notevole, abilitando il programmatore ad eseguire in modo diretto delle operazioni essenzialmente di basso livello in modo immediato. Alcune di queste operazioni non sarebbero addirittura possibili senza l’uso dei puntatori. Essi possono essere utilizzati tra le altre cose anche per scandire vettori e passare dei valori alle funzioni, e vedremo come fare queste operazioni nei prossimi articoli. Per il momento dobbiamo ancora soffermarci su un paio di aspetti importanti: come stampare a video i valori dei puntatori tramite la printf e i doppi puntatori (puntatori multipli in generale).
Per quanto riguarda il primo punto analizziamo le due istruzioni printf presenti nel codice sopra:


printf(“l’indirizzo della cella di memoria che contiene x e’ %d”, p);
printf(“l’indirizzo della cella di memoria che contiene p e’ %d”, &p);

Se vogliamo stampare a video il valore di un nostro puntatore (che sarebbe l’indirizzo contenuto al proprio interno) usiamo un’istruzione come la prima printf.
Se invece vogliamo sapere l’indirizzo della parola di memoria associata ad una qualunque delle nostre variabili (possono essere interi, caratteri o puntatori) basta usare un’istruzione come la seconda printf, che presenta il simbolo & nella parte di elencazione delle variabili da stampare, che indica che vogliamo stampare non il contenuto della variabile, ma l’indirizzo della cella di memoria ad essa associata.
In entrambi i casi si usano i caratteri %d (analogo agli interi) all’interno della stringa, in quanto ciò che stamperemo alla fine sono degli indirizzi, e quindi dei semplici numeri interi.

Chiudo con qualche parola sui doppi puntatori:
Nel C si tiene aperta la possibilità di far puntare un puntatore ad un altro puntatore, cioè ad una cella che contiene anch’essa un indirizzo, in questo modo:

int *p, **dp;
int x;

int main()
{
p=&x;
dp=&p;
**dp=100;
}

x è un intero, p è un puntatore semplice, dp è un doppio puntatore. La prima istruzione fa in modo che p punti a x, la seconda che dp punti a p, mentre con la terza si assegna ad x il valore 100, utilizzando un assegnamento tramite puntatori analogo a quelli visti prima per puntatori semplici, ma utilizzando due simboli * invece che uno: il secondo serve ad avere il valore contenuto nella cella puntata direttamente da dp, che, contenendo un indirizzo, a sua volta punta essa stessa ad un’altra cella, il cui valore viene acceduto dal secondo *.
Graficamente si ha:
img 5

Concludo (sta volta sul serio) ricordando che una variabile di tipo vettore può anche essere usata senza indicazione sugli indici di posizione ed indica l’indirizzo della prima cella di memoria del vettore stesso (gli indirizzi delle celle successive sono i numeri successivi all’indirizzo della prima, le celle sono tutte adiacenti in memoria) cioè è il puntatore al primo elemento.
Riprenderemo questo concetto nei prossimi articoli,
alla prossima!

Please follow and like us: