Membri Array Flessibili in C99
La gestione di array di dimensioni variabili nelle strutture C è cambiata grazie all'introduzione dei membri di array flessibile nello standard C99.
Questo meccanismo sostituisce lo struct hack tradizionale, offrendo una soluzione ufficiale e più pulita per allocare dinamicamente porzioni di memoria all'interno di una struttura.
Nel prosieguo vedremo come dichiarare e utilizzare i membri di array flessibile e quali vincoli si applicano a questo tipo di struttura.
Tecnica dello Struct Hack
Ogni tanto, può capitare di dover definire una struttura che contenga un array di dimensioni sconosciute.
Ad esempio, potremmo voler memorizzare stringhe in un formato diverso dal solito. Normalmente, una stringa è un array di caratteri con un carattere nullo alla fine. Tuttavia, esistono vantaggi nel memorizzare le stringhe in altri modi. Un'alternativa è memorizzare la lunghezza della stringa insieme ai caratteri (ma senza il carattere nullo).
Lunghezza e caratteri potrebbero essere memorizzati in una struttura come questa:
struct vstring {
int len;
char chars[N];
};
Qui N
è una macro che rappresenta la lunghezza massima di una stringa.
L'uso di un array a lunghezza fissa come questo è tuttavia sconsigliabile, poiché ci costringe a limitare la lunghezza della stringa e spreca memoria (dato che la maggior parte delle stringhe non occuperà tutti i N
caratteri dell'array).
I programmatori C hanno tradizionalmente risolto questo problema dichiarando la lunghezza di chars
pari a 1 (un valore fittizio) e allocando dinamicamente ogni stringa:
struct vstring {
int len;
char chars[1];
};
...
struct vstring *str = malloc(sizeof(struct vstring) + n - 1);
str->len = n;
In questo caso, stiamo barando allocando più memoria di quanta la struttura dichiari di avere (in questo esempio, n - 1
caratteri in più) e utilizzando tale memoria per conservare gli elementi aggiuntivi dell'array chars
. Questa tecnica è diventata così comune nel corso degli anni da meritare un nome: il struct hack.
Struct Hack
Lo Struct Hack è una tecnica per definire strutture in C che contengono array di dimensioni sconosciute come membro finale della struttura stessa.
Per utilizzare lo struct hack, si dichiara un array di dimensione 1 come ultimo membro della struttura. Quando si alloca memoria per la struttura, si aggiunge la dimensione effettiva dell'array richiesto meno 1 all'allocazione di memoria.
La sintassi dello struct hack è la seguente:
struct nome_struttura {
/* membri della struttura */
tipo_array nome_array[1];
};
L'allocazione di memoria per una struttura che utilizza lo struct hack è la seguente:
struct nome_struttura *p = malloc(
sizeof(struct nome_struttura) +
sizeof(tipo_array) * (dimensione - 1)
);
Lo struct hack non è limitato agli array di caratteri: ha un'ampia varietà di impieghi. Con il tempo è divenuto così diffuso che molti compilatori lo supportano. Alcuni (incluso GCC) permettono persino che l'array chars
abbia lunghezza zero, rendendo questo trucco ancora più esplicito. Sfortunatamente, lo standard C89 non garantisce il funzionamento dello struct hack, né consente array di lunghezza zero.
Ad esempio in GCC, il seguente codice è legale:
struct vstring {
int len;
char chars[0];
};
In questo caso, chars
è un array di lunghezza zero. Quando si alloca memoria per una struttura vstring
, si può scrivere:
struct vstring *str = malloc(sizeof(struct vstring) + n);
str->len = n;
In questo esempio, str
punta a una struttura vstring
in cui l'array chars
occupa n
caratteri. L'operatore sizeof
ignora l'array chars
quando calcola la dimensione della struttura.
Membri Array Flessibili in C99
Riconoscendo l'utilità dello struct hack, lo standard C99 fornisce una funzionalità nota come membro array flessibile (flexible array member), che serve allo stesso scopo. Quando l'ultimo membro di una struttura è un array, la sua lunghezza può essere omessa:
struct vstring {
int len;
char chars[]; /* membro di array flessibile – solo in C99 */
};
La lunghezza dell'array chars
non viene determinata fino a quando la memoria per una struttura vstring
non viene allocata, di solito con una chiamata a malloc
:
struct vstring *str = malloc(sizeof(struct vstring) + n);
str->len = n;
In questo esempio, str
punta a una struttura vstring
in cui l'array chars
occupa n
caratteri. L'operatore sizeof
ignora il membro chars
quando calcola la dimensione della struttura. (Un membro di array flessibile è insolito perché non occupa spazio all'interno della struttura stessa.)
Alcune regole speciali si applicano a una struttura che contiene un membro di array flessibile:
- Il membro array flessibile deve apparire come ultimo membro della struttura;
- La struttura deve avere almeno un altro membro;
- Se copiamo una struttura contenente un membro di array flessibile, verranno copiati solo gli altri membri ma non l'array flessibile in sé.
Ricapitolando:
Membro Array Flessibile in C99
Un membro array flessibile è un array di lunghezza sconosciuta che appare come ultimo membro di una struttura in C99.
La lunghezza dell'array flessibile non è specificata nella dichiarazione della struttura, ma viene determinata quando si alloca memoria per la struttura.
Per utilizzare un membro array flessibile, si dichiara un array senza specificare la sua lunghezza come ultimo membro della struttura:
struct nome_struttura {
/* membri della struttura */
tipo_array nome_array[];
};
L'allocazione di memoria per una struttura con un membro array flessibile è la seguente:
struct nome_struttura *p = malloc(
sizeof(struct nome_struttura) +
sizeof(tipo_array) * dimensione
);
Una struttura con un membro array flessibile deve avere almeno un altro membro. Quando si copia una struttura con un membro array flessibile, verranno copiati solo gli altri membri, non l'array flessibile in sé.
Introduzione ai Tipi incompleti
Una struttura che contiene un membro di array flessibile è un tipo incompleto.
Un tipo incompleto non fornisce tutte le informazioni necessarie a determinare quanta memoria gli occorra. I tipi incompleti, che tratteremo approfonditamente nelle prossime lezioni, sono soggetti a diverse restrizioni.
In particolare, un tipo incompleto (e quindi una struttura che contiene un membro di array flessibile) non può essere membro di un'altra struttura né elemento di un array. Tuttavia, qualsiasi array può contenere puntatori a strutture che abbiano un membro di array flessibile.
In Sintesi
In questa lezione abbiamo visto che:
- I membri array flessibili sono una caratteristica introdotta dallo standard C99 per gestire strutture con array di dimensioni sconosciute in fase di compilazione.
- Sostituiscono il cosiddetto struct hack, rendendo più sicuro e portabile l'uso di array dinamici all'interno delle strutture.
- Il membro flessibile deve essere l'ultimo della struttura e la struttura stessa deve contenere almeno un altro membro.
- Il tipo della struttura che contiene il membro flessibile è considerato incompleto e soggetto a diverse limitazioni (ad esempio, non può essere incluso come membro in un'altra struttura).
- Questo approccio permette di allocare la memoria necessaria per la parte flessibile al momento dell'uso, evitando sprechi e vincoli di dimensione fissa.