Perché sizeof per una struttura non è uguale alla somma di sizeof di ciascun membro?


697

Perché l' sizeofoperatore restituisce una dimensione più grande per una struttura rispetto alle dimensioni totali dei membri della struttura?


14
Vedi questa FAQ C sull'allineamento della memoria. c-faq.com/struct/align.esr.html
Richard Chambers,

48
Aneddoto: c'era un vero virus informatico che inseriva il suo codice all'interno di paddle nel programma host.
Elazar,

4
@Elazar È impressionante! Non avrei mai pensato che fosse possibile usare aree così minuscole per qualsiasi cosa. Sei in grado di fornire ulteriori dettagli?
Wilson,

1
@Wilson - Sono sicuro che ha coinvolto un sacco di jmp.
Hoodaticus,

4
Vedi l' imbottitura della struttura , l'imballo : The Lost Art of C Imballaggio della struttura Eric S. Raymond
EsmaeelE

Risposte:


649

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:

  • L'accesso errato potrebbe essere un errore grave (spesso SIGBUS).
  • L'accesso errato potrebbe essere un errore lieve.
    • O corretto in hardware, per un modesto degrado delle prestazioni.
    • O corretto mediante emulazione nel software, per un grave peggioramento delle prestazioni.
    • Inoltre, l'atomicità e altre garanzie di concorrenza potrebbero essere infrante, portando a sottili errori.

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 Znell'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 #pragmaistruzioni speciali per modificare le impostazioni di allineamento della struttura.


38
Voglio prendere nota qui: la maggior parte dei processori ti penalizza per l'accesso alla memoria non allineato (come hai detto), ma non puoi dimenticare che molti non lo accettano completamente. La maggior parte dei chip MIPS, in particolare, genererà un'eccezione per un accesso non allineato.
Cody Brocious,

35
I chip x86 sono in realtà piuttosto unici in quanto consentono l'accesso non allineato, sebbene penalizzato; La maggior parte delle chips di AFAIK genererà eccezioni, non solo alcune. PowerPC è un altro esempio comune.
Dark Shikari,

6
L'abilitazione dei pragmi per gli accessi non allineati generalmente fa sì che il tuo codice aumenti in dimensioni, su processori che generano errori di disallineamento, poiché deve essere generato un codice per correggere ogni disallineamento. ARM inoltre genera errori di disallineamento.
Mike Dimmick,

5
@ Dark - sono assolutamente d'accordo. Ma la maggior parte dei processori desktop sono x86 / x64, quindi la maggior parte dei chip non genera errori di allineamento dei dati;)
Aaron,

27
L'accesso non allineato ai dati è in genere una funzionalità presente nelle architetture CISC e la maggior parte delle architetture RISC non la include (ARM, MIPS, PowerPC, Cell). In realtà, la maggior parte dei chip NON sono processori desktop, per regole incorporate per numero di chip e la stragrande maggioranza di questi sono architetture RISC.
Lara Dougan,

191

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  |
+-------+-------+-------+-------+

1
Ora a che cosa servono gli slot di memoria pad1, pad2 e pad3.
Lakshmi Sreekanth Chitla,


@EmmEff questo potrebbe essere sbagliato ma non lo capisco del tutto: perché non c'è slot di memoria per il puntatore negli array?
Balázs Börcsök,

1
@ BalázsBörcsök Questi sono array di dimensioni costanti, quindi i loro elementi vengono memorizzati direttamente nella struttura in offset fissi. Il compilatore conosce tutto questo al momento della compilazione, quindi il puntatore è implicito. Ad esempio, se si dispone di una variabile struct di questo tipo chiamata sthen &s.a == &se &s.d == &s + 12(dato l'allineamento mostrato nella risposta). Il puntatore viene archiviato solo se le matrici hanno una dimensione variabile (ad esempio, è astata dichiarata char a[]anziché char a[3]), ma gli elementi devono essere memorizzati altrove.
Kbolino

27

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.


5
"buoni motivi" Esempio: mantenere la compatibilità binaria (padding) coerente tra i sistemi a 32 e 64 bit per una struttura complessa nel codice demo di proof-of-concept che verrà presentato domani. A volte la necessità deve avere la precedenza sulla proprietà.
Mr.Ree,

2
Va tutto bene tranne quando si parla del sistema operativo. Questo è un problema per la velocità della CPU, il sistema operativo non è coinvolto affatto.
Blaisorblade,

3
Un altro buon motivo è se si inserisce un flusso di dati in una struttura, ad esempio quando si analizzano i protocolli di rete.
CEO

1
@dolmen Ho appena sottolineato che "è più facile per il sistema Operatin accedere ai dati" non è corretto, poiché il sistema operativo non accede ai dati.
Blaisorblade,

1
@dolmen In effetti, si dovrebbe parlare dell'ABI (interfaccia binaria dell'applicazione). L'allineamento predefinito (utilizzato se non lo si modifica nell'origine) dipende dall'ABI e molti sistemi operativi supportano più ABI (ad esempio, a 32 e 64 bit o per binari di sistemi operativi diversi o per modi diversi di compilare il stessi binari per lo stesso sistema operativo). OTOH, quale allineamento è conveniente dal punto di vista delle prestazioni dipende dalla CPU: si accede alla memoria nello stesso modo se si utilizza la modalità a 32 o 64 bit (non posso commentare la modalità reale, ma sembra poco rilevante per le prestazioni al giorno d'oggi). IIRC Pentium ha iniziato preferendo l'allineamento a 8 byte.
Blaisorblade,

15

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

4
"C su Linux usando gcc" non è sufficiente per descrivere la tua piattaforma. L'allineamento dipende principalmente dall'architettura della CPU.
dolmen,

- @ Kyle Burton. Mi scusi, non capisco perché la dimensione della struttura "someBits" sia uguale a 4, mi aspetto 8 byte poiché vengono dichiarati 2 numeri interi (2 * sizeof (int)) = 8 byte. grazie
youpilat13

1
Ciao @ youpilat13, :2e in :6realtà 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
Kyle Burton

11

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.


4
"Sono abbastanza sicuro che l'ordine dei membri sia grugnito in C". Sì, C99 dice: "All'interno di un oggetto struttura, i membri senza campo bit e le unità in cui risiedono i campi bit hanno indirizzi che aumentano nell'ordine in cui sono dichiarati". Bontà più standard a: stackoverflow.com/a/37032302/895245
Ciro Santilli郝海东冠状病六四事件法轮功


8

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.


Questo discute le conseguenze dell'imbottitura della struttura, ma non risponde alla domanda.
Keith Thompson,

" ... a causa di ciò che si chiama imballaggio. ... - Penso che tu intenda" imbottitura "." Dimensione preferita dei processori più moderni se 32 bit (4 byte) "- È un po 'una semplificazione eccessiva. Tipicamente sono supportate dimensioni di 8, 16, 32 e 64 bit; spesso ogni dimensione ha il suo allineamento. E non sono sicuro che la tua risposta aggiunga nuove informazioni che non sono già nella risposta accettata.
Keith Thompson

1
Quando ho detto imballaggio, intendevo come il compilatore impacchetta i dati in una struttura (e può farlo riempiendo i piccoli oggetti, ma non ha bisogno di pad, ma fa sempre i pacchetti). Per quanto riguarda le dimensioni, stavo parlando dell'architettura del sistema, non di ciò che il sistema supporterà per l'accesso ai dati (che è molto diverso dall'architettura del bus sottostante). Per quanto riguarda il tuo commento finale, ho dato una spiegazione semplificata ed estesa di un aspetto del compromesso (velocità contro dimensioni): un grave problema di programmazione. Descrivo anche un modo per risolvere il problema, che non era nella risposta accettata.
Sid1138,

"Packing" in questo contesto di solito si riferisce all'allocazione dei membri più strettamente del valore predefinito, come nel caso di #pragma pack. Se i membri sono allocati sul loro allineamento predefinito, in genere direi che la struttura non è compressa.
Keith Thompson,

L'imballaggio è una specie di termine sovraccarico. Significa come metti in memoria gli elementi strutturali. Simile al significato di mettere oggetti in una scatola (imballaggio per lo spostamento). Significa anche mettere gli elementi in memoria senza imbottitura (una specie di mano corta per "strettamente imballato"). Quindi c'è la versione del comando della parola nel comando #pragma pack.
Sid1138,

5

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.


5

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 :-)


4

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.


8
Non proprio. Nelle implementazioni tipiche, ciò che viene aggiunto alla struttura è un puntatore vtable .
Don Wakefield,

3

Il linguaggio C lascia al compilatore un po 'di libertà sulla posizione degli elementi strutturali nella memoria:

  • buchi di memoria possono apparire tra due componenti qualsiasi e dopo l'ultimo componente. Era dovuto al fatto che alcuni tipi di oggetti sul computer di destinazione potevano essere limitati dai limiti di indirizzamento
  • dimensione dei "fori di memoria" inclusa nel risultato dell'operatore sizeof. La dimensione del solo non include la dimensione dell'array flessibile, disponibile in C / C ++
  • Alcune implementazioni del linguaggio consentono di controllare il layout di memoria delle strutture attraverso le opzioni del pragma e del compilatore

Il linguaggio C fornisce una certa sicurezza al programmatore del layout degli elementi nella struttura:

  • i compilatori devono assegnare una sequenza di componenti aumentando gli indirizzi di memoria
  • L'indirizzo del primo componente coincide con l'indirizzo iniziale della struttura
  • i campi di bit senza nome possono essere inclusi nella struttura agli allineamenti degli indirizzi richiesti degli elementi adiacenti

Problemi relativi all'allineamento degli elementi:

  • Computer diversi allineano i bordi degli oggetti in diversi modi
  • Diverse restrizioni sulla larghezza del campo bit
  • I computer differiscono su come memorizzare i byte in una parola (Intel 80x86 e Motorola 68000)

Come funziona l'allineamento:

  • Il volume occupato dalla struttura viene calcolato come dimensione del singolo elemento allineato di una matrice di tali strutture. La struttura dovrebbe terminare in modo tale che il primo elemento della successiva struttura successiva non violi i requisiti di allineamento

ps Informazioni più dettagliate sono disponibili qui: "Samuel P.Harbison, Guy L.Steele CA Reference, (5.6.2 - 5.6.7)"


2

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.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.