Domanda

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

È stato utile?

Soluzione

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 soft.
    • 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 utilizzate le modalità 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 ++ 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 istruzioni speciali #pragma per modificare le impostazioni di allineamento della struttura.

Altri suggerimenti

Imballaggio e allineamento dei byte, come descritto nelle FAQ C qui :

  

È per l'allineamento. Molti processori non possono accedere a 2- e 4 byte   quantità (ad es. ints e long ints) se sono stipati   ogni-che-way.

     

Supponi di avere questa struttura:

struct {
    char a[3];
    short int b;
    long int c;
    char d[3];
};
     

Ora, potresti pensare che dovrebbe essere possibile comprimerlo   struttura in memoria come questa:

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

Ma è molto più semplice sul processore se il compilatore organizza   così:

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

Nella versione compressa, nota quanto sia almeno un po 'difficile per   io e te per vedere come si avvolgono i campi bec? In poche parole,   è difficile anche per il processore. Pertanto, la maggior parte dei compilatori eseguirà il pad   la struttura (come se con campi extra, invisibili) in questo modo:

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

Se vuoi che la struttura abbia una certa dimensione con GCC ad esempio usa __attribute __ ((pacchetto)) .

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

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

Quindi sostanzialmente si tratta di un 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

Vedi anche:

per Microsoft Visual C:

http://msdn.microsoft .com / it-it / 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 dall'imballaggio, non esiste una garanzia per gli ordini dei membri in C ++ . I compilatori possono (e certamente lo fanno) aggiungere alla struttura puntatore di tabella virtuale 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 è 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 particolare processore ha una dimensione di dati preferita con cui funziona. Dimensione preferita dei processori più moderni se 32 bit (4 byte). Accedere 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.

Ad 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 confine 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 - 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 il tempo di accesso a quell'elemento di 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), dovrai probabilmente 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.

Ad 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.

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.

Anche 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.

Bozza standard C99 N1256

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

6.5.3.4 L'operatore sizeof :

  

3 Se applicato a un operando con struttura o tipo di unione,   il risultato è il numero totale di byte in tale oggetto,   compresa imbottitura interna e finale.

6.7.2.1 Specificatori di struttura e unione :

  

13 ... Potrebbe esserci un nome   imbottitura 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 C99 funzionalità membro dell'array flessibile ( struct S {int is [ ];}; ) può anche interessare il riempimento:

  

16 Come caso speciale, può essere l'ultimo elemento di una struttura con più di un membro nominato   avere un tipo di array incompleto; questo è chiamato un membro flessibile dell'array. Nella maggior parte delle situazioni,   il membro flessibile dell'array viene ignorato. In particolare, la dimensione della struttura è come se il   il membro flessibile dell'array è stato omesso tranne per il fatto che potrebbe avere più imbottitura finale di   l'omissione implicherebbe.

Problemi di portabilità dell'allegato J ribadisce:

  

I seguenti non sono specificati: ...

     
      
  • Il valore dei byte di riempimento durante la memorizzazione dei 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   per una classe, il risultato è il numero di byte in un oggetto di quella classe incluso l'eventuale riempimento richiesto   posizionando oggetti di quel tipo in un array.

9.2 Membri della classe :

  

Un puntatore a un oggetto struct a layout standard, opportunamente convertito usando un reinterpret_cast, punta al suo   membro iniziale (o se quel membro è un campo 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 adeguato. & # 8212; nota finale]

Conosco solo C ++ abbastanza 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:

  • potrebbero comparire buchi di memoria tra due componenti qualsiasi e dopo l'ultimo componente. È stato dovuto al fatto che alcuni tipi di oggetti sul computer di destinazione potrebbero essere limitati dai limiti di indirizzamento
  • "fori di memoria" dimensione inclusa nel risultato di sizeof operator. 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 garanzia al programmatore del layout degli elementi nella struttura:

  • compilatori richiesti per assegnare una sequenza di componenti che aumentano 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:

  • Diversi computer 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 che il primo elemento della seguente struttura successiva non violi i requisiti di allineamento

p.s Informazioni più dettagliate sono disponibili qui: " Samuel P.Harbison, Guy L.Steele C A 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 non allineati. Tuttavia, è più lento e quando il disallineamento si sovrappone a due diverse linee di cache, quindi sfrutta due linee di cache quando un accesso allineato ne eliminerebbe solo una.

Alcune architetture devono effettivamente intercettare letture e scritture disallineate e le prime versioni dell'architettura ARM (quella che si è evoluta in tutte le attuali CPU mobili) ... 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 che 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.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top