C Language
Imbottitura e imballaggio della struttura
Ricerca…
introduzione
Per impostazione predefinita, i compilatori C dispongono di strutture in modo che sia possibile accedere rapidamente a ciascun membro, senza incorrere in penalità per l'accesso non allineato, un problema con le macchine RISC come DEC Alpha e alcune CPU ARM.
A seconda dell'architettura della CPU e del compilatore, una struttura può occupare più spazio in memoria della somma delle dimensioni dei membri del componente. Il compilatore può aggiungere padding tra membri o alla fine della struttura, ma non all'inizio.
L'imballaggio sovrascrive il riempimento predefinito.
Osservazioni
Eric Raymond ha un articolo su The Lost Art of C Structure Packing che è una lettura utile.
Strutture di imballaggio
Di default le strutture sono riempite in C. Se vuoi evitare questo comportamento, devi richiederlo esplicitamente. Sotto GCC è __attribute__((__packed__))
. Considera questo esempio su una macchina a 64 bit:
struct foo {
char *p; /* 8 bytes */
char c; /* 1 byte */
long x; /* 8 bytes */
};
La struttura verrà automaticamente riempita per avere 8-byte
allineamento di 8-byte
e avrà il seguente aspetto:
struct foo {
char *p; /* 8 bytes */
char c; /* 1 byte */
char pad[7]; /* 7 bytes added by compiler */
long x; /* 8 bytes */
};
Quindi sizeof(struct foo)
ci darà 24
invece di 17
. Questo è accaduto a causa di un compilatore a 64 bit di lettura / scrittura da / a memoria in 8 byte di parola in ogni passaggio e ovvio quando si tenta di scrivere char c;
un byte in memoria un intero 8 byte (cioè una parola) recuperato e consuma solo il primo byte di esso e i suoi sette successivi di byte rimane vuoto e non accessibile per qualsiasi operazione di lettura e scrittura per il riempimento della struttura.
Imballaggio della struttura
Ma se aggiungi l'attributo packed
, il compilatore non aggiungerà il padding:
struct __attribute__((__packed__)) foo {
char *p; /* 8 bytes */
char c; /* 1 byte */
long x; /* 8 bytes */
};
Ora sizeof(struct foo)
restituirà 17
.
Vengono utilizzate strutture generalmente imballate:
- Per risparmiare spazio
- Formattare una struttura dati per trasmettere su rete senza dipendere da ciascun allineamento dell'architettura di ciascun nodo della rete.
È necessario tenere presente che alcuni processori come ARM Cortex-M0 non consentono l'accesso alla memoria non allineato; in questi casi, l'impacchettamento della struttura può comportare un comportamento indefinito e causare il blocco della CPU.
Imbottitura della struttura
Supponiamo che questa struct
sia definita e compilata con un compilatore a 32 bit:
struct test_32 {
int a; // 4 byte
short b; // 2 byte
int c; // 4 byte
} str_32;
Potremmo aspettarci che questa struct
occupi solo 10 byte di memoria, ma stampando sizeof(str_32)
vediamo che usa 12 byte.
Ciò è accaduto perché il compilatore allinea le variabili per l'accesso rapido. Un modello comune è che quando il tipo di base occupa N byte (dove N è una potenza di 2 come 1, 2, 4, 8, 16 e raramente più grande), la variabile deve essere allineata su un limite di N byte ( un multiplo di N byte).
Per la struttura mostrata con sizeof(int) == 4
e sizeof(short) == 2
, un layout comune è:
-
int a;
memorizzato all'offset 0; taglia 4. -
short b;
memorizzato all'offset 4; taglia 2. - imbottitura senza nome all'offset 6; taglia 2.
-
int c;
memorizzato all'offset 8; taglia 4.
Quindi struct test_32
occupa 12 byte di memoria. In questo esempio, non vi è alcun riempimento finale.
Il compilatore garantirà che tutte le variabili struct test_32
siano memorizzate a partire da un limite di 4 byte, in modo che i membri all'interno della struttura siano correttamente allineati per l'accesso rapido. Le funzioni di allocazione della memoria come malloc()
, calloc()
e realloc()
sono necessarie per garantire che il puntatore restituito sia sufficientemente allineato per l'uso con qualsiasi tipo di dati, quindi anche le strutture allocate dinamicamente saranno allineate correttamente.
È possibile ritrovarsi con situazioni strane come su un processore Intel x86_64 a 64 bit (ad es. Intel Core i7 - un Mac con MacOS Sierra o Mac OS X), dove quando si compila in modalità a 32 bit, i compilatori vengono posizionati in modo double
su un Limite di 4 byte; ma, sullo stesso hardware, durante la compilazione in modalità a 64 bit, i compilatori vengono posizionati in modo double
su un limite di 8 byte.