Perché l' sizeof
operatore restituisce una dimensione più grande per una struttura rispetto alle dimensioni totali dei membri della struttura?
Perché l' sizeof
operatore restituisce una dimensione più grande per una struttura rispetto alle dimensioni totali dei membri della struttura?
Risposte:
Ciò è dovuto al riempimento aggiunto per soddisfare i vincoli di allineamento. L'allineamento della struttura dei dati influisce sia sulle prestazioni che sulla correttezza dei programmi:
SIGBUS
).Ecco un esempio che utilizza le impostazioni tipiche per un processore x86 (tutte le modalità a 32 e 64 bit utilizzate):
struct X
{
short s; /* 2 bytes */
/* 2 padding bytes */
int i; /* 4 bytes */
char c; /* 1 byte */
/* 3 padding bytes */
};
struct Y
{
int i; /* 4 bytes */
char c; /* 1 byte */
/* 1 padding byte */
short s; /* 2 bytes */
};
struct Z
{
int i; /* 4 bytes */
short s; /* 2 bytes */
char c; /* 1 byte */
/* 1 padding byte */
};
const int sizeX = sizeof(struct X); /* = 12 */
const int sizeY = sizeof(struct Y); /* = 8 */
const int sizeZ = sizeof(struct Z); /* = 8 */
Si può minimizzare la dimensione delle strutture ordinando i membri per allineamento (l'ordinamento per dimensione è sufficiente per quello nei tipi di base) (come la struttura Z
nell'esempio sopra).
NOTA IMPORTANTE: entrambi gli standard C e C ++ affermano che l'allineamento della struttura è definito dall'implementazione. Pertanto, ciascun compilatore può scegliere di allineare i dati in modo diverso, risultando in layout di dati diversi e incompatibili. Per questo motivo, quando si ha a che fare con librerie che verranno utilizzate da diversi compilatori, è importante capire come i compilatori allineano i dati. Alcuni compilatori hanno impostazioni della riga di comando e / o #pragma
istruzioni speciali per modificare le impostazioni di allineamento della struttura.
Imballaggio e allineamento dei byte, come descritto nelle FAQ C qui :
È per l'allineamento. Molti processori non possono accedere a quantità a 2 e 4 byte (ad es. Ints e long ints) se sono stipati in ogni modo.
Supponiamo di avere questa struttura:
struct { char a[3]; short int b; long int c; char d[3]; };
Ora, potresti pensare che dovrebbe essere possibile comprimere questa struttura nella memoria in questo modo:
+-------+-------+-------+-------+ | a | b | +-------+-------+-------+-------+ | b | c | +-------+-------+-------+-------+ | c | d | +-------+-------+-------+-------+
Ma è molto, molto più semplice sul processore se il compilatore lo organizza in questo modo:
+-------+-------+-------+ | a | +-------+-------+-------+ | b | +-------+-------+-------+-------+ | c | +-------+-------+-------+-------+ | d | +-------+-------+-------+
Nella versione compatta, noti come sia almeno un po 'difficile per te e me vedere come si avvolgono i campi bec? In poche parole, è difficile anche per il processore. Pertanto, la maggior parte dei compilatori riempirà la struttura (come se con campi extra, invisibili) in questo modo:
+-------+-------+-------+-------+ | a | pad1 | +-------+-------+-------+-------+ | b | pad2 | +-------+-------+-------+-------+ | c | +-------+-------+-------+-------+ | d | pad3 | +-------+-------+-------+-------+
s
then &s.a == &s
e &s.d == &s + 12
(dato l'allineamento mostrato nella risposta). Il puntatore viene archiviato solo se le matrici hanno una dimensione variabile (ad esempio, è a
stata dichiarata char a[]
anziché char a[3]
), ma gli elementi devono essere memorizzati altrove.
Ad esempio, se si desidera che la struttura abbia una determinata dimensione con GCC __attribute__((packed))
.
Su Windows è possibile impostare l'allineamento su un byte quando si utilizza la compier cl.exe con l' opzione / Zp .
Di solito è più facile per la CPU accedere a dati multipli di 4 (o 8), a seconda della piattaforma e anche del compilatore.
Quindi è sostanzialmente una questione di allineamento.
Devi avere buoni motivi per cambiarlo.
Ciò può essere dovuto all'allineamento e al riempimento dei byte in modo che la struttura esca su un numero pari di byte (o parole) sulla piattaforma. Ad esempio in C su Linux, le seguenti 3 strutture:
#include "stdio.h"
struct oneInt {
int x;
};
struct twoInts {
int x;
int y;
};
struct someBits {
int x:2;
int y:6;
};
int main (int argc, char** argv) {
printf("oneInt=%zu\n",sizeof(struct oneInt));
printf("twoInts=%zu\n",sizeof(struct twoInts));
printf("someBits=%zu\n",sizeof(struct someBits));
return 0;
}
Hanno membri le cui dimensioni (in byte) sono rispettivamente 4 byte (32 bit), 8 byte (2x 32 bit) e 1 byte (2 + 6 bit). Il programma sopra (su Linux usando gcc) stampa le dimensioni come 4, 8 e 4 - dove l'ultima struttura è imbottita in modo che sia una sola parola (4 x 8 bit byte sulla mia piattaforma a 32 bit).
oneInt=4
twoInts=8
someBits=4
:2
e in :6
realtà stanno specificando 2 e 6 bit, in questo caso non interi a 32 bit. someBits.x, essendo solo 2 bit è possibile memorizzare solo 4 possibili valori: 00, 01, 10 e 11 (1, 2, 3 e 4). ha senso? Ecco un articolo sulla funzione: geeksforgeeks.org/bit-fields-c
Guarda anche:
per Microsoft Visual C:
http://msdn.microsoft.com/en-us/library/2e70t5y1%28v=vs.80%29.aspx
e GCC rivendicano la compatibilità con il compilatore di Microsoft .:
http://gcc.gnu.org/onlinedocs/gcc/Structure_002dPacking-Pragmas.html
Oltre alle risposte precedenti, tieni presente che, indipendentemente dalla confezione, in C ++ non esiste una garanzia per gli ordini dei membri . I compilatori possono (e certamente lo fanno) aggiungere alla struttura puntatori di tabelle virtuali e membri delle strutture di base. Anche l'esistenza della tabella virtuale non è garantita dallo standard (l'implementazione del meccanismo virtuale non è specificata) e quindi si può concludere che tale garanzia è semplicemente impossibile.
Sono abbastanza sicuro che l' ordine dei membri sia garantito in C , ma non ci contare su quando scrivo un programma multipiattaforma o cross-compilatore.
La dimensione di una struttura è maggiore della somma delle sue parti a causa di ciò che viene chiamato imballaggio. Un determinato processore ha una dimensione di dati preferita con cui funziona. Dimensione preferita dei processori più moderni se 32 bit (4 byte). L'accesso alla memoria quando i dati si trovano su questo tipo di confine è più efficiente delle cose che si trovano a cavallo di quel limite di dimensioni.
Per esempio. Considera la struttura semplice:
struct myStruct
{
int a;
char b;
int c;
} data;
Se la macchina è una macchina a 32 bit e i dati sono allineati su un limite di 32 bit, vediamo un problema immediato (supponendo che non vi siano allineamenti della struttura). In questo esempio, supponiamo che i dati della struttura inizino all'indirizzo 1024 (0x400 - si noti che i 2 bit più bassi sono zero, quindi i dati sono allineati a un limite di 32 bit). L'accesso a data.a funzionerà bene perché inizia su un limite - 0x400. Anche l'accesso a data.b funzionerà bene, perché si trova all'indirizzo 0x404, un altro limite a 32 bit. Ma una struttura non allineata metterebbe data.c all'indirizzo 0x405. I 4 byte di data.c sono 0x405, 0x406, 0x407, 0x408. Su una macchina a 32 bit, il sistema legge data.c durante un ciclo di memoria, ma ottiene solo 3 dei 4 byte (il quarto byte si trova sul limite successivo). Quindi, il sistema dovrebbe fare un secondo accesso alla memoria per ottenere il 4 ° byte,
Ora, se invece di mettere data.c all'indirizzo 0x405, il compilatore riempie la struttura di 3 byte e inserisce data.c all'indirizzo 0x408, allora il sistema avrebbe bisogno di solo 1 ciclo per leggere i dati, riducendo i tempi di accesso a quell'elemento dati del 50%. L'imbottitura scambia l'efficienza della memoria per l'efficienza di elaborazione. Dato che i computer possono avere enormi quantità di memoria (molti gigabyte), i compilatori ritengono che lo scambio (velocità rispetto alle dimensioni) sia ragionevole.
Sfortunatamente, questo problema diventa un killer quando si tenta di inviare strutture su una rete o addirittura di scrivere i dati binari in un file binario. Il riempimento inserito tra elementi di una struttura o classe può interrompere i dati inviati al file o alla rete. Per scrivere un codice portatile (uno che andrà a diversi compilatori diversi), probabilmente dovrai accedere a ciascun elemento della struttura separatamente per garantire il corretto "imballaggio".
D'altra parte, diversi compilatori hanno abilità diverse per gestire il packaging della struttura dei dati. Ad esempio, in Visual C / C ++ il compilatore supporta il comando #pragma pack. Ciò ti consentirà di regolare l'imballaggio e l'allineamento dei dati.
Per esempio:
#pragma pack 1
struct MyStruct
{
int a;
char b;
int c;
short d;
} myData;
I = sizeof(myData);
Ora dovrei avere la lunghezza di 11. Senza il pragma, potrei essere qualsiasi da 11 a 14 (e per alcuni sistemi, fino a 32), a seconda del pacchetto predefinito del compilatore.
#pragma pack
. Se i membri sono allocati sul loro allineamento predefinito, in genere direi che la struttura non è compressa.
Può farlo se hai impostato implicitamente o esplicitamente l'allineamento della struttura. Una struttura allineata a 4 sarà sempre un multiplo di 4 byte anche se la dimensione dei suoi membri sarebbe qualcosa che non è un multiplo di 4 byte.
Inoltre, una libreria può essere compilata in x86 con ints a 32 bit e potresti confrontare i suoi componenti su un processo a 64 bit ti darebbe un risultato diverso se lo facessi a mano.
Tiraggio standard C99 N1256
http://www.open-std.org/JTC1/SC22/WG14/www/docs/n1256.pdf
6.5.3.4 La dimensione dell'operatore :
3 Se applicato a un operando che ha una struttura o un tipo di unione, il risultato è il numero totale di byte in tale oggetto, incluso il riempimento interno e finale.
6.7.2.1 Specificatori di struttura e unione :
13 ... Potrebbe esserci un'imbottitura senza nome all'interno di un oggetto struttura, ma non all'inizio.
e:
15 Potrebbe esserci un'imbottitura senza nome alla fine di una struttura o unione.
La nuova funzione membro di array flessibile C99 ( struct S {int is[];};
) può influire anche sul riempimento:
16 Come caso speciale, l'ultimo elemento di una struttura con più di un membro nominato può avere un tipo di array incompleto; questo è chiamato un membro flessibile dell'array. Nella maggior parte dei casi, il membro flessibile dell'array viene ignorato. In particolare, la dimensione della struttura è come se il membro flessibile dell'array fosse omesso, tranne per il fatto che potrebbe avere più imbottitura finale di quanto l'omissione implicherebbe.
L'allegato J Problemi di portabilità ribadisce:
Non sono specificati i seguenti: ...
- Il valore dei byte di riempimento durante la memorizzazione di valori in strutture o sindacati (6.2.6.1)
Bozza standard C ++ 11 N3337
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3337.pdf
5.3.3 Dimensione di :
2 Se applicato a una classe, il risultato è il numero di byte in un oggetto di quella classe, incluso l'eventuale riempimento necessario per posizionare oggetti di quel tipo in un array.
9.2 Membri della classe :
Un puntatore a un oggetto struct a layout standard, opportunamente convertito usando reinterpret_cast, punta al suo membro iniziale (o se quel membro è un campo di bit, quindi all'unità in cui risiede) e viceversa. [Nota: potrebbe quindi esserci un'imbottitura senza nome all'interno di un oggetto struct a layout standard, ma non all'inizio, se necessario per ottenere un allineamento appropriato. - nota finale]
Conosco solo abbastanza C ++ per capire la nota :-)
Oltre alle altre risposte, una struttura può (ma di solito non ha) funzioni virtuali, nel qual caso la dimensione della struttura includerà anche lo spazio per vtbl.
Il linguaggio C lascia al compilatore un po 'di libertà sulla posizione degli elementi strutturali nella memoria:
Il linguaggio C fornisce una certa sicurezza al programmatore del layout degli elementi nella struttura:
Problemi relativi all'allineamento degli elementi:
Come funziona l'allineamento:
ps Informazioni più dettagliate sono disponibili qui: "Samuel P.Harbison, Guy L.Steele CA Reference, (5.6.2 - 5.6.7)"
L'idea è che per considerazioni sulla velocità e sulla cache, gli operandi dovrebbero essere letti da indirizzi allineati alle loro dimensioni naturali. A tal fine, il compilatore esegue il pading dei membri della struttura in modo che il seguente membro o la seguente struttura saranno allineati.
struct pixel {
unsigned char red; // 0
unsigned char green; // 1
unsigned int alpha; // 4 (gotta skip to an aligned offset)
unsigned char blue; // 8 (then skip 9 10 11)
};
// next offset: 12
L'architettura x86 è sempre stata in grado di recuperare indirizzi disallineati. Tuttavia, è più lento e quando il disallineamento si sovrappone a due diverse linee di cache, quindi elimina due linee di cache quando un accesso allineato ne sfruterebbe solo una.
Alcune architetture in realtà devono intercettare letture e scritture disallineate e le prime versioni dell'architettura ARM (quella che si è evoluta in tutte le CPU mobili di oggi) ... beh, in realtà hanno appena restituito dati errati per quelli. (Hanno ignorato i bit di ordine inferiore.)
Infine, nota che le linee della cache possono essere arbitrariamente grandi e il compilatore non tenta di indovinarle o di fare un compromesso spazio-velocità. Invece, le decisioni di allineamento fanno parte dell'ABI e rappresentano l'allineamento minimo che alla fine riempirà uniformemente una riga della cache.
TL; DR: l' allineamento è importante.