Una struct passata a una funzione viene copiata. Va bene per un punto di due int ed è uno spreco per un record da 200 byte, e rende impossibile la modifica, perché la funzione vede sempre e solo la propria copia. Puntare a una struct risolve entrambi i problemi in un colpo solo, ed è anche l'unico modo per costruire strutture che crescono: liste, alberi, grafi.
Prendere un puntatore a una struct
Non serve nulla di speciale. & ti dà l'indirizzo, e il tipo è "puntatore a struct":
Tutte e tre stampano 3. Le ultime due sono la stessa operazione scritta in due modi.
Perché (*ptr).x ha bisogno delle parentesi
Potresti provare a toglierle e scrivere *ptr.x. Compila in qualcosa di molto diverso o, più spesso, non compila affatto, perché l'operatore di membro . ha precedenza più alta della dereferenziazione *:
*ptr.x // interpretato come *(ptr.x) -- sbagliato
(*ptr).x // prima dereferenzia, poi prende il membro -- corretto
ptr->x // la stessa cosa, detta in breve
ptr.x chiede un membro del puntatore, e un puntatore non ha membri. La lamentela del compilatore ("request for member 'x' in something not a structure or union") è un bug di precedenza travestito.
Dato che la forma con le parentesi è brutta e facile da sbagliare, il C fornisce ->. In pratica non vedrai quasi mai (*p).x nel codice reale; usa p->x e dimentica che la forma lunga esiste, se non come spiegazione di cosa significa la freccia.
Passare una struct per puntatore
Un parametro puntatore dà alla funzione la struct vera del chiamante, quindi le modifiche restano:
Due abitudini da prendere qui:
- Dichiara
consti puntatori in sola lettura.const struct Point *ppromette che la funzione non cambierà ciò a cui puntap. Se qualcuno in seguito aggiungep->x = 0all'interno, la compilazione fallisce invece di far arrivare il bug in produzione. Documenta anche l'intenzione nel punto di chiamata. - Passa un puntatore per le struct grandi anche quando devi solo leggere. Una struct con un buffer per il nome da 64 byte costa 64 byte di copia a ogni chiamata per valore; un puntatore ne costa 8.
Le struct piccole vanno comunque bene per valore: struct Point da 8 byte si copia a buon mercato quanto un puntatore, e la versione per valore non può essere NULL, il che elimina un'intera categoria di errori.
Puntatori dentro array di struct
L'aritmetica dei puntatori funziona sugli array di struct esattamente come sugli array di int: p + 1 avanza di un'intera struct, padding compreso.
Il nome di un array decade a puntatore al primo elemento, quindi nella chiamata staff è già uno struct Employee *. È anche il motivo per cui la funzione ha bisogno di ricevere n separatamente: sizeof dentro la funzione misurerebbe il puntatore, non l'array.
Allocare una struct sull'heap
Le struct sullo stack muoiono alla fine del loro scope. Per crearne una che sopravviva alla funzione che l'ha creata, o per crearne quante ne richiede l'input, alloca con malloc:
In quella piccola funzione quattro dettagli sono tutti fondamentali:
sizeof *e, nonsizeof(struct Employee). Si legge come "la dimensione di ciò a cui puntae", quindi se il tipo cambia l'allocazione lo segue in automatico. Non c'è modo che i due si disallineino.- Controlla
NULL.mallocrestituisceNULLquando non riesce a soddisfare la richiesta. Scriveree->idtramite un puntatore nullo è un segmentation fault. - Inizializza ogni membro.
mallocnon azzera la memoria; la struct arriva piena di spazzatura. (calloc(1, sizeof *e)la azzera per te.) - Qualcuno deve chiamare
free. Restituire un puntatore allocato trasferisce quel compito al chiamante. Dillo in un commento: una regola di proprietà poco chiara è il modo in cui iniziano i memory leak.
Il premio: il nodo di una lista concatenata
Ecco la cosa che le struct non possono fare senza puntatori. Una struct non può contenere se stessa, perché sarebbe infinitamente grande, ma può contenere un puntatore a una del suo stesso tipo, e questo unico trucco costruisce ogni struttura dati collegata in C.
Nota che struct Node *next; fa riferimento a struct Node mentre struct Node è ancora in fase di dichiarazione. È lecito proprio perché un puntatore ha una dimensione nota indipendentemente da ciò a cui punta: il compilatore non ha ancora bisogno della definizione completa. È anche l'unico punto in cui devi usare il tag struct Node anche se hai scritto un typedef: il nome del typedef non esiste ancora dentro la sua stessa definizione.
Il ciclo di free_list salva head->next prima di chiamare free(head). Leggere head->next dopo il free è un use-after-free: la memoria non è più tua, e il valore che leggi è qualunque cosa ci abbia messo l'allocatore.
Errori comuni
p->xsu un puntatore non inizializzato oNULL. La causa più comune di crash nel codice pieno di struct. Inizializza i puntatori aNULLe controlla prima di dereferenziare.- Restituire un puntatore a una struct locale.
struct Point *bad(void) { struct Point p = {1,2}; return &p; }restituisce l'indirizzo di memoria che smette di esistere nel momento in cui la funzione restituisce. Restituisci la struct per valore, oppure allocala. - Liberare due volte, o dimenticare di liberare. Ogni
mallocva in coppia con esattamente unfree. Dopo aver liberato, imposta il puntatore aNULL, così un uso accidentale successivo va in crash in modo evidente invece di corrompere la memoria in silenzio. - Copiare una struct che contiene un puntatore. L'assegnazione di struct copia il valore del puntatore, non ciò a cui punta. Ora entrambe le struct fanno riferimento allo stesso buffer, e quella che lo libera per prima lascia l'altra con un puntatore pendente.
Domande frequenti
Cosa fa l'operatore -> in C?
-> in C?p->x accede al membro x tramite il puntatore p. È esattamente equivalente a (*p).x: dereferenzia il puntatore, poi prende il membro. La freccia esiste perché i puntatori a struct sono così comuni che la forma lunga appesantisce il codice.
Perché *p.x non funziona con un puntatore a struct?
*p.x non funziona con un puntatore a struct?Perché . lega più forte di *. *p.x viene interpretato come *(p.x): prende il membro x di p (che è un puntatore, non una struct) e dereferenzia quello. Ti serve (*p).x, oppure scrivi semplicemente p->x.
Meglio passare una struct per valore o per puntatore in C?
Passala per puntatore quando la funzione deve modificare la struct del chiamante, o quando la struct è grande e copiarla sarebbe uno spreco. Passa per valore le struct piccole in sola lettura, dove la copia costa poco e il codice è più semplice. Dichiara i parametri puntatore in sola lettura come const struct T *, così il compilatore fa rispettare la promessa.
Come si alloca una struct con malloc in C?
struct Node *n = malloc(sizeof *n);: la forma sizeof *n chiede la dimensione di ciò a cui punta n, quindi resta corretta anche se in seguito cambi il tipo. Confronta il risultato con NULL, inizializza ogni membro e chiama free(n) quando hai finito.