Perché i puntatori C sono importanti: come il pass-by-pointer consente la modifica dei dati reali

12

La maggior parte degli sviluppatori incontra i puntatori in C attraverso un meccanismo specifico noto come pass-by-pointer. Non è solo una stranezza della sintassi; è la ragione fondamentale per cui le funzioni possono modificare lo stato esterno. Probabilmente l’hai già visto senza rendertene conto. Considera la funzione “scanf”. Ogni volta che chiami scanf, sei costretto ad anteporre l’operatore indirizzo di (& ) alle tue variabili.

scanf("%d", &mioNumero);

Se ometti quella e commerciale, il programma si blocca. Perché? Perché “scanf” si aspetta un indirizzo di memoria, non un valore. Comprendere questo comportamento sblocca la logica dietro come C gestisce i parametri delle funzioni. Quando passi un puntatore, stai concedendo a una funzione il permesso di raggiungere la tua memoria e riscrivere direttamente i dati.

Il fallimento del passaggio per valore

Per capire perché i puntatori sono necessari, dobbiamo guardare cosa succede quando sono assenti. Un errore comune da principiante è tentare di scrivere una funzione “scambia” che scambia due valori interi.

Ecco un’implementazione ingenua che fallisce:

Esegui questo codice. L’output mostrerà “5 10” seguito da “5 10”. Lo scambio non è avvenuto.

Il motivo è semplice. In C, gli argomenti vengono passati per valore per impostazione predefinita. Quando viene chiamato “swap(a, b)”, il sistema crea copie locali di “a” e “b” denominate “i” e “j”. La funzione scambia perfettamente i valori di “i” e “j”. Ma quando la funzione ritorna, quelle variabili locali vengono distrutte. La “a” e la “b” originali in “main” rimangono intatte.

La soluzione: pass-by-pointer

Per modificare le variabili originali, la funzione deve accedere alle loro reali posizioni di memoria, non alle copie dei loro valori. È qui che i puntatori per i parametri delle funzioni diventano essenziali.

Ecco l’implementazione corretta:

Ora l’output è “5 10” seguito da “10 5”. Lo scambio ha funzionato.

Come funziona il dereferenziamento dei puntatori

La differenza sta in ciò che viene passato alla funzione.

  1. La chiamata: swap(&a, &b) passa gli indirizzi di a e b.
  2. I parametri: La firma della funzione void swap(int *i, int *j) dichiara i e j come puntatori a numeri interi.
  3. La connessione: All’interno di “swap”, “i” contiene l’indirizzo di “a”. “j” contiene l’indirizzo di “b”.
  4. La dereferenziazione: L’affermazione *i = *j significa “vai all’indirizzo di memoria memorizzato in i e scrivi al suo interno il valore trovato nell’indirizzo di memoria memorizzato in j.”

Visivamente, immagina due scatole etichettate “a” e “b”.
– “a” contiene “5”.
– “b” contiene “10”.
– “i” è una nota su cui è scritto l’indirizzo della casella “a”.
– “j” è una nota su cui è scritto l’indirizzo della casella “b”.

Quando il codice esegue t = *i, legge la nota “i”, va in quella posizione e legge il valore “5”. Memorizza “5” in “t”. Quindi *i = *j legge il valore dalla casella “b” (che è “10”) e lo riscrive nella casella “a”. Le scatole originali sono cambiate.

Il pericolo di perdere l’indirizzo