Imbottitura e imballaggio della struttura


209

Tener conto di:

struct mystruct_A
{
   char a;
   int b;
   char c;
} x;

struct mystruct_B
{
   int b;
   char a;
} y;

Le dimensioni delle strutture sono rispettivamente 12 e 8.

Queste strutture sono imbottite o imballate?

Quando avviene l'imbottitura o l'imballaggio?



24
The Lost Art of C Imballaggio della struttura - catb.org/esr/structure-packing
Paolo

paddingrende le cose più grandi. packingrende le cose più piccole. Totalmente differente.
smwikipedia,

Risposte:


264

Il padding allinea i membri della struttura ai limiti di indirizzo "naturali" - diciamo, i intmembri avrebbero offset, che sono mod(4) == 0su piattaforma a 32 bit. Il riempimento è attivo per impostazione predefinita. Inserisce i seguenti "gap" nella tua prima struttura:

struct mystruct_A {
    char a;
    char gap_0[3]; /* inserted by compiler: for alignment of b */
    int b;
    char c;
    char gap_1[3]; /* -"-: for alignment of the whole struct in an array */
} x;

L'imballaggio , d'altra parte impedisce al compilatore di eseguire il riempimento - questo deve essere esplicitamente richiesto - sotto GCC è __attribute__((__packed__)), quindi il seguente:

struct __attribute__((__packed__)) mystruct_A {
    char a;
    int b;
    char c;
};

produrrebbe una struttura di dimensioni 6su un'architettura a 32 bit.

Una nota però: l'accesso alla memoria non allineato è più lento per le architetture che lo consentono (come x86 e amd64) ed è esplicitamente vietato su architetture di allineamento rigoroso come SPARC.


2
Mi chiedo: il divieto di memoria non allineata sulla scintilla significa che non può gestire un normale array di byte? L'impacchettamento di strutture, come noto, viene utilizzato principalmente nella trasmissione (ad es. Di reti) di dati, quando è necessario eseguire il cast di una matrice di byte in una struttura ed essere sicuri che una matrice si adatti ai campi di una struttura. Se la scintilla non può farlo, come funzionano quelli?
Hi-Angel,

14
Questo è esattamente il motivo per cui, se guardi i layout delle intestazioni IP, UDP e TCP, vedresti che tutti i campi interi sono allineati.
Nikolai Fetissov,

17
"Lost Art of C Structure Packing" spiega l'imbottitura e l'imballaggio delle ptimisations - catb.org/esr/structure-packing
Rob11311

3
Il primo membro deve venire per primo? Ho pensato che l'arrangiamento dipendesse totalmente dall'implementazione e non si può fare affidamento (anche da una versione all'altra).
codice alleato

4
+ allyourcode Lo standard garantisce che l'ordine dei membri verrà preservato e che il primo membro inizi a offset 0.
martinkunev,

64

( Le risposte di cui sopra hanno spiegato il motivo abbastanza chiaramente, ma non sembrano del tutto chiare sulle dimensioni dell'imbottitura, quindi aggiungerò una risposta in base a quanto appreso da The Lost Art of Structure Packing , che si è evoluto per non limitarsi a C, ma applicabile anche ai Go, Rust. )


Allinea memoria (per struct)

Regole:

  • Prima di ogni singolo membro, ci sarà un'imbottitura in modo da farlo iniziare a un indirizzo che è divisibile per le sue dimensioni.
    ad es. su un sistema a 64 bit, intdovrebbe iniziare all'indirizzo divisibile per 4 e longper 8, shortper 2.
  • chare char[]sono speciali, potrebbe essere qualsiasi indirizzo di memoria, quindi non hanno bisogno di riempimento prima di loro.
  • Infatti struct, oltre alla necessità di allineamento per ogni singolo membro, la dimensione dell'intera struttura stessa sarà allineata a una dimensione divisibile per dimensione del singolo membro più grande, mediante imbottitura alla fine.
    ad es. se il membro più grande di struct è longquindi divisibile per 8, intquindi per 4, shortquindi per 2.

Ordine dei membri:

  • L'ordine dei membri potrebbe influire sulla dimensione effettiva della struttura, quindi tienilo a mente. ad esempio, l'esempio stu_ce stu_ddi seguito hanno gli stessi membri, ma in un ordine diverso e comportano dimensioni diverse per i 2 elementi.

Indirizzo in memoria (per struct)

Regole:

  • Sistema a 64 bit
    L'indirizzo Struct inizia dai (n * 16)byte. ( Si può vedere nell'esempio seguente, tutti gli indirizzi esadecimali stampati delle strutture terminano con 0. )
    Motivo : il possibile membro strutt più grande possibile è 16 byte ( long double).
  • (Aggiornamento) Se uno struct contiene solo uncharmembro come, il suo indirizzo potrebbe iniziare da qualsiasi indirizzo.

Spazio vuoto :

  • Lo spazio vuoto tra 2 strutture potrebbe essere utilizzato da variabili non strutt che potrebbero adattarsi. Ad
    es . In test_struct_address()basso, la variabile xrisiede tra la struttura adiacente ge h.
    Indipendentemente dal fatto che xvenga dichiarato, hl'indirizzo non cambierà, ma xsolo riutilizzato lo spazio vuoto gsprecato.
    Caso simile per y.

Esempio

( per sistema a 64 bit )

memory_align.c :

/**
 * Memory align & padding - for struct.
 * compile: gcc memory_align.c
 * execute: ./a.out
 */ 
#include <stdio.h>

// size is 8, 4 + 1, then round to multiple of 4 (int's size),
struct stu_a {
    int i;
    char c;
};

// size is 16, 8 + 1, then round to multiple of 8 (long's size),
struct stu_b {
    long l;
    char c;
};

// size is 24, l need padding by 4 before it, then round to multiple of 8 (long's size),
struct stu_c {
    int i;
    long l;
    char c;
};

// size is 16, 8 + 4 + 1, then round to multiple of 8 (long's size),
struct stu_d {
    long l;
    int i;
    char c;
};

// size is 16, 8 + 4 + 1, then round to multiple of 8 (double's size),
struct stu_e {
    double d;
    int i;
    char c;
};

// size is 24, d need align to 8, then round to multiple of 8 (double's size),
struct stu_f {
    int i;
    double d;
    char c;
};

// size is 4,
struct stu_g {
    int i;
};

// size is 8,
struct stu_h {
    long l;
};

// test - padding within a single struct,
int test_struct_padding() {
    printf("%s: %ld\n", "stu_a", sizeof(struct stu_a));
    printf("%s: %ld\n", "stu_b", sizeof(struct stu_b));
    printf("%s: %ld\n", "stu_c", sizeof(struct stu_c));
    printf("%s: %ld\n", "stu_d", sizeof(struct stu_d));
    printf("%s: %ld\n", "stu_e", sizeof(struct stu_e));
    printf("%s: %ld\n", "stu_f", sizeof(struct stu_f));

    printf("%s: %ld\n", "stu_g", sizeof(struct stu_g));
    printf("%s: %ld\n", "stu_h", sizeof(struct stu_h));

    return 0;
}

// test - address of struct,
int test_struct_address() {
    printf("%s: %ld\n", "stu_g", sizeof(struct stu_g));
    printf("%s: %ld\n", "stu_h", sizeof(struct stu_h));
    printf("%s: %ld\n", "stu_f", sizeof(struct stu_f));

    struct stu_g g;
    struct stu_h h;
    struct stu_f f1;
    struct stu_f f2;
    int x = 1;
    long y = 1;

    printf("address of %s: %p\n", "g", &g);
    printf("address of %s: %p\n", "h", &h);
    printf("address of %s: %p\n", "f1", &f1);
    printf("address of %s: %p\n", "f2", &f2);
    printf("address of %s: %p\n", "x", &x);
    printf("address of %s: %p\n", "y", &y);

    // g is only 4 bytes itself, but distance to next struct is 16 bytes(on 64 bit system) or 8 bytes(on 32 bit system),
    printf("space between %s and %s: %ld\n", "g", "h", (long)(&h) - (long)(&g));

    // h is only 8 bytes itself, but distance to next struct is 16 bytes(on 64 bit system) or 8 bytes(on 32 bit system),
    printf("space between %s and %s: %ld\n", "h", "f1", (long)(&f1) - (long)(&h));

    // f1 is only 24 bytes itself, but distance to next struct is 32 bytes(on 64 bit system) or 24 bytes(on 32 bit system),
    printf("space between %s and %s: %ld\n", "f1", "f2", (long)(&f2) - (long)(&f1));

    // x is not a struct, and it reuse those empty space between struts, which exists due to padding, e.g between g & h,
    printf("space between %s and %s: %ld\n", "x", "f2", (long)(&x) - (long)(&f2));
    printf("space between %s and %s: %ld\n", "g", "x", (long)(&x) - (long)(&g));

    // y is not a struct, and it reuse those empty space between struts, which exists due to padding, e.g between h & f1,
    printf("space between %s and %s: %ld\n", "x", "y", (long)(&y) - (long)(&x));
    printf("space between %s and %s: %ld\n", "h", "y", (long)(&y) - (long)(&h));

    return 0;
}

int main(int argc, char * argv[]) {
    test_struct_padding();
    // test_struct_address();

    return 0;
}

Risultato dell'esecuzione test_struct_padding():

stu_a: 8
stu_b: 16
stu_c: 24
stu_d: 16
stu_e: 16
stu_f: 24
stu_g: 4
stu_h: 8

Risultato dell'esecuzione test_struct_address():

stu_g: 4
stu_h: 8
stu_f: 24
address of g: 0x7fffd63a95d0  // struct variable - address dividable by 16,
address of h: 0x7fffd63a95e0  // struct variable - address dividable by 16,
address of f1: 0x7fffd63a95f0 // struct variable - address dividable by 16,
address of f2: 0x7fffd63a9610 // struct variable - address dividable by 16,
address of x: 0x7fffd63a95dc  // non-struct variable - resides within the empty space between struct variable g & h.
address of y: 0x7fffd63a95e8  // non-struct variable - resides within the empty space between struct variable h & f1.
space between g and h: 16
space between h and f1: 16
space between f1 and f2: 32
space between x and f2: -52
space between g and x: 12
space between x and y: 12
space between h and y: 8

Quindi l'indirizzo di inizio per ogni variabile è g: d0 x: dc h: e0 y: e8

inserisci qui la descrizione dell'immagine


4
"Regole" in realtà lo ha reso molto chiaro, non sono riuscito a trovare una regola semplice da nessuna parte. Grazie.
Pervez Alam,

2
@PervezAlam Il libro <The Lost Art of C Structure Packing>, spiega abbastanza bene le regole, anche se è un po 'più lungo di questa risposta. Il libro è disponibile gratuitamente online: catb.org/esr/structure-packing
Eric Wang

Ci proverò, tra l'altro è limitato all'imballaggio della struttura? Solo curiosità perché mi è piaciuta la spiegazione nel libro.
Pervez Alam,

1
@PervezAlam È un libro molto breve, focalizzato principalmente sulla tecnologia che ridurrebbe il footprint di memoria del programma c, ci vogliono solo al massimo diversi giorni per terminare la lettura.
Eric Wang,

1
@ValidusOculus Sì, significa 16 byte allineati.
Eric Wang,

44

So che questa domanda è vecchia e la maggior parte delle risposte qui spiega molto bene il riempimento, ma mentre cercavo di capirlo da solo, ho pensato che avere un'immagine "visiva" di ciò che stava accadendo fosse di aiuto.

Il processore legge la memoria in "blocchi" di dimensioni definite (parola). Supponiamo che la parola del processore sia lunga 8 byte. Guarderà la memoria come una grande fila di blocchi di 8 byte. Ogni volta che ha bisogno di ottenere alcune informazioni dalla memoria, raggiungerà uno di quei blocchi e le otterrà.

Allineamento delle variabili

Come sembra nell'immagine sopra, non importa dove sia un Char (lungo 1 byte), poiché sarà all'interno di uno di quei blocchi, richiedendo alla CPU di elaborare solo 1 parola.

Quando abbiamo a che fare con dati più grandi di un byte, come un int 4 byte o un doppio 8 byte, il modo in cui sono allineati nella memoria fa la differenza su quante parole dovranno essere elaborate dalla CPU. Se i blocchi di 4 byte sono allineati in modo da adattarsi sempre all'interno di un blocco (l'indirizzo di memoria è un multiplo di 4) sarà necessario elaborare solo una parola. Altrimenti un blocco di 4 byte potrebbe avere parte di se stesso su un blocco e parte su un altro, richiedendo al processore di elaborare 2 parole per leggere questi dati.

Lo stesso vale per un doppio da 8 byte, tranne ora che deve trovarsi in un indirizzo di memoria multiplo di 8 per garantire che rimanga sempre all'interno di un blocco.

Questo considera un elaboratore di testi a 8 byte, ma il concetto si applica ad altre dimensioni di parole.

L'imbottitura funziona riempiendo gli spazi tra questi dati per assicurarsi che siano allineati con quei blocchi, migliorando così le prestazioni durante la lettura della memoria.

Tuttavia, come affermato nelle altre risposte, a volte lo spazio è più importante della performance stessa. Forse stai elaborando molti dati su un computer che non ha molta RAM (lo spazio di swap potrebbe essere utilizzato ma è MOLTO più lento). È possibile disporre le variabili nel programma fino a quando non viene eseguito il padding minimo (come è stato ampiamente esemplificato in alcune altre risposte) ma se ciò non bastasse è possibile disabilitare esplicitamente il padding, che è il pack .


3
Questo non spiega l'imballaggio della struttura ma illustra abbastanza bene l'allineamento delle parole della CPU.
David Foerster,

L'hai disegnato con la vernice? :-)
Ciro Santilli 30 冠状 病 六四 事件 法轮功

1
@ CiroSantilli709 大 抓捕 六四 事件 法轮功, era su gimp, ma immagino che mi sarei risparmiato un po 'di tempo a dipingere anche se ahah
IanC

1
Ancora meglio
dall'open

21

L'imballaggio della struttura elimina l'imbottitura della struttura, l'imbottitura utilizzata quando l'allineamento conta di più, l'imballaggio utilizzato quando lo spazio è importante.

Alcuni compilatori forniscono #pragmaper sopprimere il riempimento o per renderlo impacchettato in n numero di byte. Alcuni forniscono parole chiave per farlo. Generalmente il pragma utilizzato per modificare il riempimento della struttura sarà nel formato seguente (dipende dal compilatore):

#pragma pack(n)

Ad esempio ARM fornisce la __packedparola chiave per sopprimere il riempimento della struttura. Consulta il manuale del compilatore per saperne di più.

Quindi una struttura imballata è una struttura senza imbottitura.

Verranno utilizzate strutture generalmente imballate

  • per risparmiare spazio

  • formattare una struttura di dati da trasmettere in rete utilizzando un protocollo (questa non è una buona pratica, ovviamente, perché è necessario
    affrontare l'endianness)


5

Imbottitura e imballaggio sono solo due aspetti della stessa cosa:

  • l'imballaggio o l'allineamento sono le dimensioni a cui viene arrotondato ciascun elemento
  • imbottitura è lo spazio aggiuntivo aggiunto per abbinare l'allineamento

In mystruct_A, presupponendo un allineamento predefinito di 4, ogni membro è allineato su un multiplo di 4 byte. Poiché la dimensione di charè 1, il riempimento per ae cè 4 - 1 = 3 byte mentre non è richiesto alcun riempimento per il int bquale sono già 4 byte. Funziona allo stesso modo per mystruct_B.


1

L'imballaggio della struttura viene eseguito solo quando si dice esplicitamente al compilatore di imballare la struttura. L'imbottitura è ciò che stai vedendo. Il sistema a 32 bit riempie ogni campo per allineare le parole. Se avessi detto al tuo compilatore di impacchettare le strutture, sarebbero rispettivamente 6 e 5 byte. Non farlo però. Non è portatile e fa in modo che i compilatori generino codice molto più lento (e talvolta anche errato).


1

Non ci sono ma a riguardo! Chi vuole cogliere l'argomento deve fare quanto segue,


1

Regole per l'imbottitura:

  1. Ogni membro della struttura dovrebbe avere un indirizzo divisibile per le sue dimensioni. Il riempimento viene inserito tra gli elementi o alla fine della struttura per assicurarsi che questa regola sia rispettata. Questo viene fatto per un accesso al bus più semplice ed efficiente da parte dell'hardware.
  2. L'imbottitura alla fine della struttura viene decisa in base alle dimensioni del membro più grande della struttura.

Perché regola 2: considerare la seguente struttura,

Struct 1

Se dovessimo creare un array (di 2 strutture) di questa struttura, alla fine non sarà richiesto alcun padding:

Array Struct1

Pertanto, la dimensione di struct = 8 byte

Supponiamo che dovessimo creare un'altra struttura come di seguito:

Struct 2

Se dovessimo creare un array di questa struttura, ci sono 2 possibilità, del numero di byte di padding richiesti alla fine.

A. Se aggiungiamo 3 byte alla fine e lo allineamo per int e non Long:

Array Struct2 allineato a int

B. Se aggiungiamo 7 byte alla fine e lo allineamo per Long:

Array Struct2 allineato a Long

L'indirizzo iniziale del secondo array è un multiplo di 8 (ovvero 24). La dimensione di struct = 24 byte

Pertanto, allineando l'indirizzo iniziale dell'array successivo della struttura a un multiplo del membro più grande (ovvero se dovessimo creare un array di questa struttura, il primo indirizzo del secondo array deve iniziare da un indirizzo multiplo del più grande membro della struttura. Eccolo, 24 (3 * 8)), possiamo calcolare il numero di byte di riempimento richiesti alla fine.


-1

L'allineamento della struttura dei dati è il modo in cui i dati sono organizzati e accessibili nella memoria del computer. Consiste in due problemi separati ma correlati: allineamento dei dati e riempimento della struttura dei dati . Quando un computer moderno legge o scrive su un indirizzo di memoria, lo farà in blocchi di dimensioni di parole (ad es. Blocchi di 4 byte su un sistema a 32 bit) o ​​più grandi. Allineamento dei dati significa mettere i dati a un indirizzo di memoria uguale a un multiplo della dimensione della parola, il che aumenta le prestazioni del sistema a causa del modo in cui la CPU gestisce la memoria. Per allineare i dati, potrebbe essere necessario inserire alcuni byte senza significato tra la fine dell'ultima struttura di dati e l'inizio della successiva, ovvero il riempimento della struttura di dati.

  1. Per allineare i dati in memoria, uno o più byte vuoti (indirizzi) vengono inseriti (o lasciati vuoti) tra gli indirizzi di memoria che sono allocati per altri membri della struttura durante l'allocazione della memoria. Questo concetto si chiama imbottitura struttura.
  2. L'architettura di un processore per computer è tale da poter leggere 1 parola (4 byte in un processore a 32 bit) dalla memoria alla volta.
  3. Per sfruttare questo vantaggio del processore, i dati sono sempre allineati come pacchetto di 4 byte che porta a inserire indirizzi vuoti tra l'indirizzo di un altro membro.
  4. A causa di questo concetto di imbottitura della struttura in C, la dimensione della struttura non è sempre la stessa di ciò che pensiamo.

1
Perché è necessario collegare lo stesso articolo 5 volte nella risposta? Si prega di mantenere un solo link all'esempio. Inoltre, poiché stai collegando al tuo articolo, devi rivelare questo fatto.
Artjom B.
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.