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



sizeof c++ ita (9)

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


Answer #1

Ciò è dovuto al riempimento aggiunto per soddisfare i vincoli di allineamento. L'allineamento della struttura dei dati influenza sia le prestazioni che la correttezza dei programmi:

  • Un accesso errato potrebbe essere un errore SIGBUS (spesso SIGBUS ).
  • L'accesso errato potrebbe essere un errore morbido.
    • O corretti in hardware, per una modesta riduzione delle prestazioni.
    • O corretto dall'emulazione nel software, per un grave peggioramento delle prestazioni.
    • Inoltre, l'atomicità e altre garanzie di concorrenza potrebbero essere interrotte, portando a errori sottili.

Ecco un esempio che utilizza le impostazioni tipiche per un processore x86 (tutte le modalità utilizzate a 32 e 64 bit):

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 ++ indicano che l'allineamento della struttura è definito dall'implementazione. Pertanto, ogni compilatore può scegliere di allineare i dati in modo diverso, con conseguente layout di dati diversi e incompatibili. Per questo motivo, quando si tratta di librerie che verranno utilizzate da diversi compilatori, è importante capire in che modo i compilatori allineano i dati. Alcuni compilatori dispongono di impostazioni della riga di comando e / o di speciali istruzioni #pragma per modificare le impostazioni di allineamento della struttura.


Answer #2

Tiraggio standard C99 N1256

http://www.open-std.org/JTC1/SC22/WG14/www/docs/n1256.pdf

6.5.3.4 L'operatore sizeof :

3 Quando viene applicato a un operando con struttura o tipo di unione, il risultato è il numero totale di byte in tale oggetto, inclusi il riempimento interno e finale.

6.7.2.1 Specificatori di struttura e unione :

13 ... Potrebbe esserci un padding senza nome all'interno di un oggetto struttura, ma non all'inizio.

e:

15 Potrebbe esserci un padding senza nome alla fine di una struttura o unione.

La nuova funzione di membro di array flessibile C99 ( struct S {int is[];}; ) può anche influire sul padding:

16 Come caso speciale, l'ultimo elemento di una struttura con più di un membro nominato potrebbe avere un tipo di array incompleto; questo è chiamato membro di array flessibile. Nella maggior parte dei casi, il membro della matrice flessibile viene ignorato. In particolare, la dimensione della struttura è come se il membro della matrice flessibile venisse omesso, tranne che potrebbe avere un riempimento più finale rispetto a quello che l'omissione implicherebbe.

Allegato J Problemi di portabilità ribadisce:

Quanto segue non è specificato: ...

  • Il valore dei byte di riempimento quando si memorizzano valori in strutture o unioni (6.2.6.1)

C ++ 11 N3337 draft standard

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3337.pdf

5.3.3 Dimensione di :

2 Quando applicato a una classe, il risultato è il numero di byte in un oggetto di quella classe che include qualsiasi riempimento richiesto per posizionare oggetti di quel tipo in una matrice.

9.2 Membri della classe :

Un puntatore a un oggetto struct con layout standard, opportunamente convertito usando un 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 pertanto esserci un padding senza nome all'interno di un oggetto struct con layout standard, ma non all'inizio, come necessario per ottenere l'allineamento appropriato. - nota finale]

Conosco abbastanza C ++ per capire la nota :-)


Answer #3

Questo può essere dovuto all'allineamento dei byte e al padding in modo tale che la struttura arrivi a 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;
}

I membri con le dimensioni (in byte) sono 4 byte (32 bit), 8 byte (2x 32 bit) e 1 byte (2 + 6 bit), rispettivamente. Il programma sopra (su Linux usando gcc) stampa le dimensioni come 4, 8 e 4 - dove l'ultima struttura è riempita in modo che sia una singola parola (4 x 8 bit di byte sulla mia piattaforma a 32 bit).

oneInt=4
twoInts=8
someBits=4

Answer #4

L'idea è che per considerazioni sulla velocità e la cache, gli operandi dovrebbero essere letti da indirizzi allineati alle loro dimensioni naturali. Per fare in modo che ciò accada, i pad del compilatore strutturano i membri in modo tale che il membro seguente o la struttura seguente siano 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 non allineati. Tuttavia, è più lento e quando il disallineamento si sovrappone a due diverse linee di cache, quindi evince due linee di cache quando un accesso allineato ne sfrutta solo uno.

Alcune architetture in realtà devono intrappolare letture e scritture non allineate e le prime versioni dell'architettura ARM (quella che si è evoluta in tutte le attuali CPU mobili) ... beh, in realtà hanno appena restituito dati non validi per quelle. (Hanno ignorato i bit di basso ordine).

Infine, si noti che le linee della cache possono essere arbitrariamente grandi, e il compilatore non tenta di indovinare quelle o 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 linea di cache.

TL; DR: l' allineamento è importante.


Answer #5

Imballaggio e allineamento dei byte, come descritto nella FAQ C here :

È per l'allineamento. Molti processori non possono accedere a quantità da 2 e 4 byte (ad esempio int 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 impacchettare questa struttura in memoria in questo modo:

+-------+-------+-------+-------+
|           a           |   b   |
+-------+-------+-------+-------+
|   b   |           c           |
+-------+-------+-------+-------+
|   c   |           d           |
+-------+-------+-------+-------+

Ma è molto, molto più facile sul processore se il compilatore lo organizza in questo modo:

+-------+-------+-------+
|           a           |
+-------+-------+-------+
|       b       |
+-------+-------+-------+-------+
|               c               |
+-------+-------+-------+-------+
|           d           |
+-------+-------+-------+

Nella versione confezionata, nota come sia almeno un po 'difficile per te e per me vedere come i campi b e c si intrecciano? In poche parole, è difficile anche per il processore. Pertanto, la maggior parte dei compilatori riempirà la struttura (come se con campi extra invisibili) come questa:

+-------+-------+-------+-------+
|           a           | pad1  |
+-------+-------+-------+-------+
|       b       |     pad2      |
+-------+-------+-------+-------+
|               c               |
+-------+-------+-------+-------+
|           d           | pad3  |
+-------+-------+-------+-------+

Answer #6

Può farlo se hai impostato implicitamente o esplicitamente l'allineamento della struttura. Una struct che è allineata 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 sotto x86 con 32 bit int e si potrebbe confrontare i suoi componenti in un processo a 64 bit sarebbe dare un risultato diverso se lo facessi a mano.


Answer #7

La dimensione di una struttura è maggiore della somma delle sue parti a causa di ciò che viene chiamato imballaggio. Un particolare processore ha una dimensione di dati preferita con cui lavora. 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 di quelli 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 a 32 bit, vediamo un problema immediato (supponendo che non ci sia allineamento della struttura). In questo esempio, supponiamo che i dati della struttura inizino all'indirizzo 1024 (0x400 - nota 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à correttamente perché inizia su un limite - 0x400. Anche l'accesso a data.b funzionerà correttamente, poiché è all'indirizzo 0x404, un altro limite a 32 bit. Ma una struttura non allineata inserirà data.c all'indirizzo 0x405. I 4 byte di data.c sono a 0x405, 0x406, 0x407, 0x408. Su una macchina a 32 bit, il sistema leggerebbe data.c durante un ciclo di memoria, ma otterrebbe solo 3 dei 4 byte (il 4o byte si trova sul confine successivo). Quindi, il sistema dovrebbe fare un secondo accesso alla memoria per ottenere il 4 ° byte,

Ora, se invece di inserire data.c all'indirizzo 0x405, il compilatore ha riempito la struttura di 3 byte e inserito data.c all'indirizzo 0x408, quindi il sistema avrebbe solo bisogno di 1 ciclo per leggere i dati, riducendo il tempo di accesso a quell'elemento dati del 50%. Il riempimento 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 swap (velocità sopra la dimensione) 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 padding inserito tra elementi di una struttura o classe può interrompere i dati inviati al file o alla rete. Per scrivere codice portatile (uno che andrà a diversi compilatori), probabilmente dovrai accedere a ciascun elemento della struttura separatamente per garantire il corretto "imballaggio".

D'altra parte, diversi compilatori hanno diverse abilità per gestire la struttura della struttura dei dati. Ad esempio, in Visual C / C ++ il compilatore supporta il comando #pragma pack. Ciò ti consentirà di adattare 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);

Dovrei ora avere la lunghezza di 11. Senza il pragma, potrei essere qualsiasi cosa da 11 a 14 (e per alcuni sistemi, fino a 32), a seconda dell'imballaggio predefinito del compilatore.


Answer #8

Oltre alle altre risposte, una struct può (ma di solito non ha) funzioni virtuali, nel qual caso la dimensione della struct includerà anche lo spazio per il vtbl.


Answer #9

Se si desidera che la struttura abbia una determinata dimensione con GCC, ad esempio, utilizzare __attribute__((packed)) .

Su Windows è possibile impostare l'allineamento su un byte quando si utilizza il comp cl.exe con l' opzione / Zp .

Di solito è più facile per la CPU accedere a dati che sono multipli di 4 (o 8), a seconda della piattaforma e anche del compilatore.

Quindi è una questione di allineamento in fondo.

Devi avere dei buoni motivi per cambiarlo.





c++-faq