Skip to content

Imbottitura e imballaggio della struttura

Dopo aver cercato in vari repository e siti internet, abbiamo finalmente trovato la risposta che presto vi mostreremo.

Soluzione:

Imbottitura allinea i membri della struttura ai confini "naturali" degli indirizzi - ad esempio, int i membri avrebbero degli offset, che sono mod(4) == 0 sulla piattaforma a 32 bit. Il padding è attivo per impostazione predefinita. Inserisce i seguenti "vuoti" nella 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;

Imballaggio, d'altra parte, impedisce al compilatore di eseguire il padding: questo deve essere richiesto esplicitamente - sotto GCC è __attribute__((__packed__)), quindi quanto segue:

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

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

Una nota però: l'accesso alla memoria non allineata è più lento sulle architetture che lo consentono (come x86 e amd64) ed è esplicitamente proibito su architetture ad allineamento stretto come SPARC.

(Le risposte precedenti hanno spiegato il motivo in modo abbastanza chiaro, ma non sembrano del tutto chiare sulla dimensione del padding, quindi aggiungerò una risposta in base a ciò che ho imparato da L'arte perduta dell'impacchettamento delle strutture si è evoluto in modo da non limitarsi a Cma anche a Go, Rust.)


Allineamento della memoria (per struct)

Regole:

  • Prima di ogni singolo membro, ci sarà un padding in modo da farlo iniziare a un indirizzo che sia divisibile per la sua dimensione.
    Ad esempio, su un sistema a 64 bit,int dovrebbe iniziare a un indirizzo divisibile per 4 e long per 8, short per 2.
  • char e char[] sono speciali, possono essere qualsiasi indirizzo di memoria, quindi non necessitano di imbottitura prima di essi.
  • Per struct, a parte l'allineamento necessario per ogni singolo membro, la dimensione dell'intera struttura sarà allineata a una dimensione divisibile per la dimensione del singolo membro più grande, con un padding alla fine.
    Ad esempio, se il membro più grande della struttura è long allora è divisibile per 8, int allora divisibile per 4, short poi per 2.

Ordine dei membri:

  • L'ordine dei membri può influire sulla dimensione effettiva della struttura, quindi tenetelo presente.
    Ad esempio, i membri stu_c e stu_d dell'esempio seguente hanno gli stessi membri, ma in ordine diverso, e risultano in dimensioni diverse per le due strutture.

Indirizzo in memoria (per la struct)

Regole:

  • Sistema a 64 bit
    L'indirizzo della struttura inizia da (n * 16) byte. (Nell'esempio seguente, tutti gli indirizzi esadecimali stampati delle strutture terminano con 0.)
    Motivo: il singolo membro della struct più grande possibile è di 16 byte (long double).
  • (Aggiornamento) Se una struct contiene solo un membro char come membro, il suo indirizzo può iniziare da qualsiasi indirizzo.

Spazio vuoto:

  • Lo spazio vuoto tra due strutture può essere utilizzato da variabili non strutturali che possono inserirsi.
    ad esempio in test_struct_address() sotto, la variabile x risiede tra le strutture adiacenti g e h.
    Non importa se x sia dichiarato, hnon cambierà, x ha semplicemente riutilizzato lo spazio vuoto che g ha sprecato.
    Caso simile per y.

Esempio

(per un sistema a 64 bit)

memory_align.c:

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

// 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: %ldn", "stu_a", sizeof(struct stu_a));
    printf("%s: %ldn", "stu_b", sizeof(struct stu_b));
    printf("%s: %ldn", "stu_c", sizeof(struct stu_c));
    printf("%s: %ldn", "stu_d", sizeof(struct stu_d));
    printf("%s: %ldn", "stu_e", sizeof(struct stu_e));
    printf("%s: %ldn", "stu_f", sizeof(struct stu_f));

    printf("%s: %ldn", "stu_g", sizeof(struct stu_g));
    printf("%s: %ldn", "stu_h", sizeof(struct stu_h));

    return 0;
}

// test - address of struct,
int test_struct_address() {
    printf("%s: %ldn", "stu_g", sizeof(struct stu_g));
    printf("%s: %ldn", "stu_h", sizeof(struct stu_h));
    printf("%s: %ldn", "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: %pn", "g", &g);
    printf("address of %s: %pn", "h", &h);
    printf("address of %s: %pn", "f1", &f1);
    printf("address of %s: %pn", "f2", &f2);
    printf("address of %s: %pn", "x", &x);
    printf("address of %s: %pn", "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: %ldn", "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: %ldn", "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: %ldn", "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: %ldn", "x", "f2", (long)(&x) - (long)(&f2));
    printf("space between %s and %s: %ldn", "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: %ldn", "x", "y", (long)(&y) - (long)(&x));
    printf("space between %s and %s: %ldn", "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 partenza per ogni variabile è g:d0 x:dc h:e0 y:e8

Immettere la descrizione dell'immagine qui

So che questa domanda è vecchia e che la maggior parte delle risposte qui spiega molto bene l'imbottitura, ma mentre cercavo di capire da solo ho pensato che avere un'immagine "visiva" di ciò che sta accadendo fosse utile.

Il processore legge la memoria in "pezzi" di dimensioni definite (parola). Diciamo che la parola del processore è lunga 8 byte. Il processore considererà la memoria come una grande fila di blocchi di 8 byte. Ogni volta che deve ottenere un'informazione dalla memoria, raggiunge uno di questi blocchi e lo ottiene.

Allineamento delle variabili

Come si vede nell'immagine precedente, non importa dove si trovi un carattere (lungo 1 byte), poiché si troverà all'interno di uno di questi blocchi, richiedendo alla CPU di elaborare solo una parola.

Quando abbiamo a che fare con dati più grandi di un byte, come un int da 4 byte o un double da 8 byte, il modo in cui sono allineati nella memoria fa la differenza sul numero di parole che la CPU dovrà elaborare. Se i pezzi da 4 byte sono allineati in modo da rientrare sempre all'interno di un blocco (l'indirizzo di memoria è un multiplo di 4), dovrà essere elaborata solo una parola. Altrimenti, un pacchetto di 4 byte potrebbe trovarsi in parte su un blocco e in parte su un altro, richiedendo al processore di elaborare due parole per leggere questi dati.

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

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

Il padding funziona riempiendo gli spazi vuoti tra questi dati per assicurarsi che siano allineati con i blocchi, migliorando così le prestazioni durante la lettura della memoria.

Tuttavia, come indicato in altre risposte, a volte lo spazio conta più delle prestazioni stesse. Forse si stanno elaborando molti dati su un computer che non ha molta RAM (si potrebbe usare lo spazio di swap, ma è MOLTO più lento). Si potrebbero organizzare le variabili nel programma in modo da ridurre al minimo il padding (come è stato esemplificato in altre risposte), ma se non è sufficiente si potrebbe disabilitare esplicitamente il padding, che è quello che si fa con imballaggio è.

Sezione recensioni e valutazioni

Ricorda che hai la possibilità di aggiungere una valutazione corretta se hai trovato la risposta.



Utilizzate il nostro motore di ricerca

Ricerca
Generic filters

Lascia un commento

Il tuo indirizzo email non sarà pubblicato.