I puntatori hanno una fama un po' intimidatoria. Tante persone che imparano il C o il C++ ci si bloccano, mollano, e poi ci tornano qualche mese dopo accorgendosi che, in fondo, non era cosรฌ terribile. La veritร sta in una frase: un puntatore si basa su una sola idea, semplicissima. Invece di lavorare con un valore, si lavora con il posto in cui quel valore รจ conservato. Tutto il resto discende in modo logico da questo principio.
Una variabile, una casella, un indirizzo
Quando si dichiara una variabile, il computer riserva una piccola zona nella memoria per conservarne il valore. Possiamo immaginare la memoria come una lunga via di palazzi, dove ogni casella ha un numero unico: il suo indirizzo. La variabile, invece, รจ soltanto un'etichetta posata su una di quelle caselle, cosรฌ non dobbiamo ricordare il numero a memoria.
Un puntatore รจ semplicemente una variabile che contiene quel numero anzichรฉ un valore normale. Dove un intero conserva il numero 42, un puntatore conserva l'indirizzo del posto in cui si trova, per esempio, proprio quel 42. Se hai giร letto la nostra guida per scrivere il tuo primo programma in C++, sei giร a metร strada: un puntatore resta una variabile, solo con un compito un po' particolare.
Dichiarare un puntatore e leggere ciรฒ che indica
In C, due simboli fanno quasi tutto il lavoro. L'asterisco * dichiara un puntatore e accede al valore che indica; la e commerciale & recupera l'indirizzo di una variabile esistente.
int eta = 42;
int *p = η // p contiene l'indirizzo di eta
printf("%d\n", *p); // stampa 42, il valore puntato
*p = 43; // modifichiamo eta tramite il puntatore
printf("%d\n", eta); // stampa 43
Qui *p si legge ยซ il valore conservato all'indirizzo contenuto in p ยป. Questa operazione si chiama dereferenziazione. Il punto da ricordare: modificare *p modifica eta direttamente, perchรฉ entrambi indicano la stessa casella di memoria. ร proprio questo che rende i puntatori cosรฌ utili.
A cosa servono davvero i puntatori
Potrebbe sembrare una complicazione gratuita. In pratica, i puntatori risolvono problemi molto concreti. Permettono a una funzione di modificare una variabile che le viene passata, invece di riceverne una semplice copia. Evitano di ricopiare grossi blocchi di dati: si passa un indirizzo, pochi byte, anzichรฉ un intero array. E rendono possibili le strutture che crescono e si riducono mentre il programma gira, come le liste concatenate e gli alberi. Se stai appena iniziando, la nostra guida alle variabili mette in chiaro le basi prima di affrontare questo argomento.
L'allocazione dinamica della memoria
Finora le nostre variabili avevano una dimensione nota in anticipo. Ma come si fa quando, nel momento in cui si scrive il codice, non si sa quanti elementi servirร conservare? La risposta sta nell'allocazione dinamica: si chiede memoria mentre il programma gira, e poi la si restituisce una volta che non serve piรน. In C si usa malloc per riservare e free per liberare.
int *array = malloc(10 * sizeof(int)); // 10 interi
if (array != NULL) {
array[0] = 1;
// ... utilizzo ...
free(array); // restituiamo la memoria
}
In C++ abbiamo new e delete, che fanno la stessa cosa rispettando meglio i tipi.
int *valore = new int(42);
delete valore;
La regola d'oro sta in una riga: a ogni allocazione deve corrispondere una liberazione. Dimenticare il free o il delete significa lasciare memoria occupata per niente.
Le trappole che spaventano, e come evitarle
La cattiva fama dei puntatori nasce soprattutto da tre errori classici. Il primo รจ la perdita di memoria: si alloca senza mai liberare, e il programma rosicchia la memoria fino a esaurirla. Il secondo รจ il puntatore pendente, che indica una zona giร liberata; accedervi produce un risultato imprevedibile. Il terzo รจ il puntatore nullo che si dereferenzia senza controllarlo, e che provoca il famoso errore di segmentazione.
La buona notizia รจ che queste trappole si neutralizzano con abitudini semplici. Si inizializza sempre un puntatore, anche solo a NULL. Si verifica che non sia nullo prima di usarlo. E dopo un free, si rimette il puntatore a NULL per non riutilizzarlo per sbaglio. Questi riflessi diventano in fretta una seconda natura.
Cosa cambia con il C++ moderno
Il C++ recente ha addolcito molto tutto questo con i puntatori intelligenti. Uno std::unique_ptr o uno std::shared_ptr si occupa di liberare la memoria automaticamente non appena non serve piรน, eliminando in un colpo solo gran parte delle perdite.
#include <memory>
std::unique_ptr<int> valore = std::make_unique<int>(42);
// liberazione automatica: nessun delete da scrivere
Ciรฒ detto, capire i puntatori ยซ grezzi ยป resta indispensabile: i puntatori intelligenti sono costruiti sopra di essi, e il codice esistente ne รจ pieno. Conviene sempre conoscere entrambi. Lo stesso spirito anima alcune librerie C piรน datate, come quella che descriviamo nel nostro articolo su conio.h, _kbhit e getch.
Per approfondire
I puntatori non si domano in una sola lettura, ma con la pratica. Scrivi piccoli programmi, disegna la memoria su un foglio, segui ogni indirizzo a mano. Nel giro di qualche giorno, ciรฒ che sembrava oscuro diventa un riflesso.
Se preferisci un supporto cartaceo per procedere al tuo ritmo, un buon libro sul linguaggio C resta un ottimo compagno di studio, in aggiunta ai tutorial online.
In qualitร di Affiliato Amazon, riceviamo un guadagno dagli acquisti idonei.







0 Commenti