Puntatori in C
In questa pagina 8
In questa pagina 7
Indirizzi e puntatori
Definizione (puntatore, operatori di indirizzo e dereferenziazione). 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.
Esempio. Con int x = 10; int *p = &x; *p = 20; si ottiene x == 20.
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
Proprietà (aritmetica dei puntatori). Se
ppunta a un elemento di un array,p + kpunta alk-esimo elemento successivo: l'indirizzo aumenta dik * sizeof(*p)byte, non dik.
Esempio. Con int v[5] = {10, 20, 30, 40, 50}; int *p = v;, *(p + 2) vale 30 e &v[4] - &v[1] vale 3.
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
Proprietà (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.
Esempio. Con int *const q = &x; l'assegnamento *q = 7 è lecito e dà x == 7, mentre q = &y è un errore di compilazione.
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.
Versione ripasso
Indirizzi e puntatori
Un puntatore è una variabile che contiene l'indirizzo di memoria di un'altra variabile.
&x: operatore di indirizzo (si applica a variabili).*p: operatore di dereferenziazione (accede all'oggetto puntato, anche a sinistra di=).- Nella dichiarazione
int *p, q;solopè un puntatore. NULLindica un puntatore non valido; va sempre controllato prima dell'uso.
int x = 10;
int *p = &x; /* p punta a x */
*p = 20; /* modifica x tramite p */Puntatori come parametri
Per consentire a una funzione di modificare una variabile del chiamante o restituire più valori, si passa il suo indirizzo, perché il C passa per valore (vedi 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 →); è ciò che fa scanf("%d", &n).
void divisione(int a, int b, int *quoziente, int *resto) {
*quoziente = a / b;
*resto = a % b;
}Aritmetica dei puntatori
Se p punta a un array, p + k sposta l'indirizzo di byte.
- Operazioni lecite: puntatore intero, differenza tra puntatori dello stesso array, confronti (
<,==).
int v[5] = {10, 20, 30, 40, 50};
int *p = v; /* &v[0] */
*(p + 2) /* 30, come v[2] */
p++; /* punta a v[1] */Array e puntatori
- Il nome di un array decade a puntatore al primo elemento (
vequivale a&v[0]). v[i]è definito come*(v + i).- Differenze: un array non è riassegnabile;
sizeof(array)dà la dimensione totale,sizeof(puntatore)la dimensione del puntatore. - Un parametro dichiarato
int v[]è 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 →).
const e puntatori
const int *p;puntatore aintcostante (*pnon modificabile,psì).int *const q = &x;puntatore costante (qnon modificabile,*qsì).
Puntatori a puntatori e a struct
int **pp = &p;: puntatore a puntatore, utile per modificare un puntatore passato a funzione (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 →).- Per accedere ai campi di una struct tramite puntatore si usa
->: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 →).
Errori tipici
- Dereferenziare un puntatore non inizializzato o
NULL(segmentation fault). - Confondere
*p++con(*p)++. - 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 →).
- Scrivere
p = 5anziché*p = 5.
Esercizi su questo argomento
Teoria collegata
- Alberi e heap in C
- Liste concatenate in C
- Liste, pile e code
- Puntatori, struct e memoria dinamica in C
- Rappresentazione dei grafi
- Array e stringhe in C
- Dal Python al C
- Funzioni in C
- Liste concatenate
- Ordinare e cercare in C - qsort, bsearch e puntatori a funzione
- Struttura di un elaboratore
- Strutture (struct) in C
- C++ - puntatori, array e cast
- Java - generics, reflection e passaggio dei parametri
- Memoria centrale - allocazione contigua, paginazione e segmentazione
- Richiami di C per la programmazione di rete - memoria, puntatori, struct ed endianness
- Sicurezza e protezione nei sistemi operativi