Puntatori in C
In questa pagina 8
Indirizzi e puntatori
Ogni variabile occupa celle di memoria con un indirizzo (vedi Struttura di un elaboratoreModello di von Neumann: CPU, memoria centrale, bus e periferiche; ciclo fetch-decode-execute e gerarchia di memoria.Struttura di un elaboratore →). Un puntatore è una variabile che contiene un indirizzo; il suo tipo dice a quale tipo di dato punta.
int x = 10;
int *p; /* p è un puntatore a int */
p = &x; /* & = indirizzo di: ora p punta a x */
printf("%d\n", *p); /* * = dereferenziazione: il valore puntato, 10 */
*p = 20; /* modifica x tramite p */
printf("%d %p\n", x, (void *)p); /* 20 e l'indirizzo, es. 0x7ffd5e8c */ indirizzo contenuto
0x1000 20 <- x
...
0x2000 0x1000 <- p (punta a x)&x: indirizzo dix. Si applica a variabili (non a costanti o espressioni).*p: l'oggetto puntato dap, utilizzabile sia in lettura sia a sinistra di=.- Nella dichiarazione
int *p, q;solopè un puntatore:qè unint. - Un puntatore non inizializzato contiene un indirizzo casuale: dereferenziarlo è comportamento indefinito.
NULL(da<stdlib.h>o<stdio.h>) è il puntatore "che non punta a niente": va controllato prima di dereferenziare.
int *q = NULL;
if (q != NULL) {
*q = 5;
}Puntatori come parametri
Poiché il C passa per valore (Funzioni in CDefinizione e prototipo di una funzione C, tipo di ritorno e void, passaggio dei parametri sempre per valore, variabili locali, globali e static, file header e compilazione separata, funzioni ricorsive.Funzioni in C →), per far modificare a una funzione una variabile del chiamante si passa il suo indirizzo. È anche il modo di "restituire" più valori.
void divisione(int a, int b, int *quoziente, int *resto) {
*quoziente = a / b;
*resto = a % b;
}
int q, r;
divisione(17, 5, &q, &r); /* q = 3, r = 2 */È esattamente ciò che fa scanf("%d", &n) (Input e output in Cprintf con larghezza e precisione, scanf con indirizzi e valore di ritorno, lettura di righe con fgets, file con FILE*, fopen, fclose, fprintf, fscanf, controllo degli errori e della fine del file.Input e output in C →).
Aritmetica dei puntatori
Se p punta a un elemento di un array, p + k punta al k-esimo elemento successivo: l'indirizzo aumenta di k * sizeof(*p) byte, non di k.
int v[5] = {10, 20, 30, 40, 50};
int *p = v; /* = &v[0] */
*(p + 2) /* 30, come v[2] */
p++; /* ora punta a v[1] */
int d = &v[4] - &v[1]; /* 3: differenza in numero di elementi */Operazioni lecite: puntatore ± intero, differenza tra puntatori allo stesso array, confronti (<, ==) tra puntatori allo stesso array. Ci si può spostare fino a una posizione oltre l'ultimo elemento (senza dereferenziarla).
Array e puntatori
- Il nome di un array in un'espressione decade a puntatore al primo elemento:
vequivale a&v[0]. v[i]è per definizione*(v + i): l'indicizzazione funziona anche con un puntatore (p[i]).- Differenze: un array non si può riassegnare (
v = ...è errore) esizeof(v)dà la dimensione dell'intero array,sizeof(p)quella di un puntatore (tipicamente 8 byte). - Un parametro dichiarato
int v[]è in realtà unint *v(vedi Array e stringhe in CArray di dimensione fissa in memoria contigua, inizializzazione, nessun controllo sugli indici, passaggio a funzioni con la lunghezza, matrici; stringhe come array di char terminati da '\0' e funzioni di string.h.Array e stringhe in C →).
/* scorrimento con un puntatore */
int somma(const int *v, int n) {
int s = 0;
for (const int *p = v; p < v + n; p++)
s += *p;
return s;
}
/* lunghezza di una stringa con i puntatori */
int lunghezza(const char *s) {
const char *p = s;
while (*p != '\0')
p++;
return p - s;
}const e puntatori
const int *p; /* puntatore a int costante: *p = 1 vietato, p = &y permesso */
int *const q = &x; /* puntatore costante: q non può cambiare, *q sì */Nei parametri const T * documenta (e fa verificare al compilatore) che la funzione non modifica i dati puntati.
Puntatori a puntatori e a struct
int **pp = &p;: puntatore a una variabile puntatore; serve, ad esempio, a una funzione che deve modificare un puntatore del chiamante (vedi Liste concatenateLista concatenata semplice: nodi allocati dinamicamente collegati da puntatori; inserimento e rimozione in testa, scorrimento, ricerca, inserimento ordinato, deallocazione; confronto dei costi con l'array; versione in Python.Liste concatenate →).- Con un puntatore a
structsi accede ai campi con->:p->campoequivale a(*p).campo(vedi Strutture (struct) in Cstruct per raggruppare campi di tipo diverso, typedef, accesso con punto e freccia, struct come parametri e valori di ritorno, array di struct, struct allocate dinamicamente e struct autoreferenziali.Strutture (struct) in C →).
Confronto con Python
In Python ogni variabile è implicitamente un riferimento (Variabili, oggetti e riferimentiIn Python una variabile è un nome legato a un oggetto; tipo, identità e valore; assegnamento, aliasing, oggetti mutabili e immutabili, None.Variabili, oggetti e riferimenti →), ma non si possono vedere né manipolare gli indirizzi: niente aritmetica, niente puntatori non validi. In C il controllo è totale e gli errori non vengono intercettati.
Errori tipici
- Dereferenziare un puntatore non inizializzato o
NULL(segmentation fault). - Confondere
*p++(usa*p, poi incrementap) con(*p)++(incrementa il valore puntato). - Restituire l'indirizzo di una variabile locale (dangling pointer, vedi Gestione della memoria in CSegmenti di memoria di un processo (codice, dati statici, stack, heap); durata delle variabili; allocazione dinamica con malloc, calloc, realloc e free; errori classici: memory leak, dangling pointer, double free, buffer overflow; strumenti di controllo.Gestione della memoria in C →).
- Assegnare
p = 5invece di*p = 5.