web-dev-qa-db-fra.com

Structure et rembourrage

Considérer:

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

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

Les tailles des structures sont 12 et 8 respectivement.

Ces structures sont-elles rembourrées ou emballées?

Quand a lieu le rembourrage ou l'emballage?

177
Manu

Padding aligne les membres de structure aux limites d'adresses "naturelles" - disons, int les membres auraient des décalages, qui sont mod(4) == 0 sur une plate-forme 32 bits. Le rembourrage est activé par défaut. Il insère les "lacunes" suivantes dans votre première structure:

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;

Packing , en revanche, empêche le compilateur de remplir - cela doit être explicitement demandé - sous GCC, il s'agit de __attribute__((__packed__)), de sorte que:

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

produirait une structure de taille 6 sur une architecture 32 bits.

Une remarque cependant - l'accès à la mémoire non alignée est plus lent sur les architectures qui le permettent (comme x86 et AMD64), et est explicitement interdit sur architectures d'alignement strict ​​comme SPARC.

238
Nikolai Fetissov

( Les réponses ci-dessus expliquent la raison très clairement, mais ne semblent pas tout à fait claires sur la taille du remplissage, donc, je vais ajouter une réponse en fonction de ce que j'ai appris L'art perdu de l'emballage de la structure, il a évolué pour ne pas se limiter à C, mais également pour Go, Rust. )


Memory align (pour struct)

Règles:

  • Avant chaque membre individuel, il y aura un rembourrage afin de le faire commencer à une adresse qui est divisible par sa taille.
    Par exemple, sur un système 64 bits, int devrait commencer à une adresse divisible par 4, et long sur 8, short sur 2.
  • char et char[] sont spéciaux. Ils peuvent être n'importe quelle adresse mémoire. Ils n'ont donc pas besoin de remplissage avant.
  • Pour struct, mis à part le besoin d'alignement de chaque membre individuel, la taille de la structure entière elle-même sera alignée sur une taille divisible par la taille du plus grand membre individuel, par remplissage à la fin.
    Par exemple, si le membre le plus important de struct est long, il est alors divisible par 8, int, puis par 4, short, puis par 2.

Ordre du membre:

  • L'ordre des membres peut affecter la taille réelle de la structure, donc gardez cela à l'esprit. Par exemple, les stu_c et stu_d de l'exemple ci-dessous ont les mêmes membres, mais dans un ordre différent, et donnent une taille différente pour les 2 structures.

Adresse en mémoire (pour struct)

Règles:

  • Système 64 bits
    L’adresse de structure commence à partir de (n * 16) _ octets. ( vous pouvez voir dans l'exemple ci-dessous que toutes les adresses hexagonales imprimées des structures se terminent par 0. )
    Raison : le membre de structure individuel le plus grand possible est de 16 octets (long double).

Espace vide :

  • L'espace vide entre 2 structures peut être utilisé par des variables autres que des structures pouvant s'intégrer.
    Par exemple, dans test_struct_address() ci-dessous, la variable x réside entre la structure adjacente g et h.
    Peu importe que x soit déclaré ou non, l'adresse de h ne changera pas, x vient de réutiliser l'espace vide que g a perdu.
    Cas similaire pour y.

Exemple

( pour un système 64 bits )

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;
}

Résultat de l'exécution - 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

Résultat de l'exécution - 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

Ainsi, l'adresse de début pour chaque variable est g: d0 x: dc h: e0 y: e8

enter image description here

40
Eric Wang

Je sais que cette question est ancienne et que la plupart des réponses ici expliquent très bien le rembourrage, mais en essayant de la comprendre moi-même, je me suis dit que j'avais une image "visuelle" de ce qui se passait.

Le processeur lit la mémoire en "morceaux" d'une taille définie (Word). Disons que le processeur Word est long de 8 octets. Il considérera la mémoire comme une grande rangée de blocs de construction de 8 octets. Chaque fois qu'il doit extraire de l'information de la mémoire, il atteint l'un de ces blocs et l'obtient.

Variables Alignment

Comme le montre l'image ci-dessus, peu importe l'emplacement du caractère (1 octet de long), car il se trouvera à l'intérieur de l'un de ces blocs, le processeur ne devant traiter qu'un mot.

Lorsque nous traitons des données de plus d'un octet, comme un entier de 4 octets ou un double de 8 octets, leur alignement dans la mémoire détermine le nombre de mots devant être traités par la CPU. Si les blocs de 4 octets sont alignés de manière à ce qu'ils correspondent toujours à l'intérieur d'un bloc (l'adresse mémoire étant un multiple de 4), un seul mot devra être traité. Sinon, un bloc de 4 octets pourrait avoir une partie de lui-même sur un bloc et une partie sur un autre, ce qui obligerait le processeur à traiter 2 mots pour lire ces données.

Il en va de même pour un double de 8 octets, sauf qu'il doit maintenant être dans une adresse mémoire multiple de 8 pour garantir qu'il sera toujours à l'intérieur d'un bloc.

Cela considère un traitement de texte de 8 octets, mais le concept s’applique à d’autres tailles de mots.

Le remplissage remplit les espaces entre ces données pour s’assurer qu’elles sont alignées sur ces blocs, ce qui améliore les performances lors de la lecture de la mémoire.

Cependant, comme indiqué dans d'autres réponses, l'espace importe parfois plus que la performance elle-même. Vous traitez peut-être beaucoup de données sur un ordinateur qui n'a pas beaucoup de RAM (vous pouvez utiliser l'espace de swap, mais il est BEAUCOUP plus lent). Vous pouvez organiser les variables dans le programme jusqu'à ce que le moindre remplissage soit effectué (comme cela a été largement illustré dans d'autres réponses), mais si cela ne suffisait pas, vous pouvez désactiver explicitement le remplissage, ce qui est ce que compression.

38
IanC

L'empaquetage de structure supprime le rembourrage de la structure.

Certains compilateurs fournissent #pragma pour supprimer le remplissage ou le rendre compressé avec un nombre n d'octets. Certains fournissent des mots clés pour le faire. Généralement, pragma utilisé pour modifier le remplissage de la structure sera au format ci-dessous (dépend du compilateur):

#pragma pack(n)

Par exemple, ARM fournit le mot-clé __packed pour supprimer le remplissage de la structure. Consultez le manuel de votre compilateur pour en savoir plus à ce sujet.

Donc, une structure compacte est une structure sans rembourrage.

Des structures généralement emballées seront utilisées

  • économiser de l'espace

  • formater une structure de données pour la transmettre sur le réseau en utilisant un protocole (ce n'est pas une bonne pratique bien sûr car vous devez
    traiter avec endianness)

20
user2083050

Le rembourrage et l'emballage ne sont que deux aspects de la même chose:

  • l'emballage ou l'alignement est la taille à laquelle chaque membre est arrondi
  • le rembourrage est l'espace supplémentaire ajouté pour correspondre à l'alignement

Dans mystruct_A, en supposant un alignement par défaut de 4, chaque membre est aligné sur un multiple de 4 octets. Étant donné que la taille de char est égale à 1, le remplissage de a et c est de 4 - 1 = 3 octets, mais aucun remplissage n'est requis pour int b qui est déjà de 4 octets. Cela fonctionne de la même manière pour mystruct_B.

5
casablanca

Le compactage de la structure n'est effectué que lorsque vous indiquez explicitement à votre compilateur de compresser la structure. Le rembourrage est ce que vous voyez. Votre système 32 bits remplit chaque champ avec l'alignement Word. Si vous aviez demandé à votre compilateur d'empaqueter les structures, elles seraient respectivement de 6 et 5 octets. Ne fais pas ça quand même. Ce n'est pas portable et fait que les compilateurs génèrent du code beaucoup plus lent (et parfois même bogué).

1
nmichaels

Il n'y a pas de mais! Qui veut saisir le sujet doit faire les suivants,

0
snr