remplissage de la structure et de l'emballage
Question
Considérez:
struct mystruct_A
{
char a;
int b;
char c;
} x;
struct mystruct_B
{
int b;
char a;
} y;
Les tailles des structures sont respectivement de 12 et 8.
sont ces structures rembourrés ou emballés?
Quand elle lieu de remplissage ou d'emballage?
La solution
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;
emballage , l'autre compilateur empêche la main de faire padding - ceci doit être explicitement demandé - du CCAG il de __attribute__((__packed__))
, donc ce qui suit:
struct __attribute__((__packed__)) mystruct_A {
char a;
int b;
char c;
};
produirait la structure de taille 6
sur une architecture 32 bits.
Une note bien -. L'accès mémoire non alignée est plus lente sur des architectures qui le permettent (comme x86 et amd64), et est explicitement interdit sur architectures d'alignement strictes comme SPARC
Autres conseils
( Les réponses ci-dessus explique la raison très clairement, mais ne semble pas tout à fait clair sur la taille de rembourrage, donc, je vais ajouter une réponse selon ce que j'appris de The Lost Art d'emballage Structure , il a évolué de ne pas limiter à C
, mais aussi applicable aux Go
, Rust
. )
Aligner la mémoire (pour struct)
Règles:
- Avant chaque membre, il y aura un rembourrage de sorte que pour le faire démarrer à une adresse qui est divisible par sa taille.
par exemple sur 64 bits système,int
doit commencer à l'adresse divisible par 4, et parlong
8,short
par deux. -
char
etchar[]
sont spéciaux, pourrait être une adresse mémoire, ils ne ont pas besoin de rembourrage devant eux. - Pour
struct
, autre que le besoin d'alignement pour chaque membre, la taille de struct tout lui-même sera aligné sur une divisible de taille par la taille du plus grand membre individuel, par un rembourrage à la fin.
par exemple si le plus grand élément de structure est alorslong
divisible par 8,int
puis par 4, puis parshort
2.
Ordre du membre:
- L'ordre des membres pourrait affecter la taille réelle de struct, afin de prendre cela à l'esprit.
et le
stu_c
par exemplestu_d
de l'exemple ci-dessous ont les mêmes membres, mais dans un ordre différent, et le résultat en différentes tailles pour les 2 struct.
Adresse en mémoire (pour struct)
Règles:
- système 64 bits
Adresse de struct commence à partir des octets de
(n * 16)
. ( Vous pouvez voir dans l'exemple ci-dessous, toutes les adresses hexagonales imprimées struct se terminent par0
. )
Raison : le plus grand membre de struct individuel possible est de 16 octets (long double
) .
- (Mise à jour) Si un struct ne contient que
char
en tant que membre, son adresse pourrait commencer à toute adresse.
Espace vide :
- L'espace vide entre 2 struct pourrait être utilisé par des variables non-struct qui pourraient s'intégrer.
par exemple danstest_struct_address()
ci-après, réside variablex
entreg
eth
struct adjacent.
Peu importe six
est déclarée, l'adresse deh
ne changera pas,x
simplement réutilisé l'espace vide quig
gaspillée.
Cas similaire poury
.
Exemple
( 64 système binaire )
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 d'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 d'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
début d'adresse Ainsi, pour chaque variable est g: d0 x: dc h: e0 y: E8
Je sais que cette question est ancienne et l'image la plupart des réponses ici explique très bien le rembourrage, mais en essayant de comprendre qu'il me je me suis dit avoir une « visuelle » de ce qui se passe aidé.
Le processeur lit la mémoire de « morceaux » d'une taille déterminée (mot). Dites le mot du processeur est de 8 octets. Il se penchera sur la mémoire comme une grande rangée de 8 octets des blocs de construction. Chaque fois qu'il a besoin d'obtenir des informations de la mémoire, il atteindra l'un de ces blocs et l'obtenir.
Comme semblent dans l'image ci-dessus, peu importe où un Char (1 octet long) est, car il sera à l'intérieur d'un de ces blocs, ce qui nécessite la CPU pour traiter seulement 1 mot.
Lorsque nous traitons avec des données plus d'un octet, comme un 4 octets int ou double 8 octets, la façon dont ils sont alignés dans la mémoire fait une différence sur le nombre de mots devront être traitées par la CPU. Si des morceaux de 4 octets sont alignés de manière qu'ils correspondent toujours à l'intérieur d'un bloc (adresse de mémoire étant un multiple de 4), un seul mot aura à traiter. Sinon, un bloc de 4 octets peut avoir une partie de lui-même sur un bloc et sur une autre partie, ce qui nécessite le processeur pour traiter les 2 mots à lire ces données.
Le même applique à un double de 8 octets, sauf que maintenant il doit être un multiple d'adresse de mémoire de 8 pour garantir qu'il sera toujours dans un bloc.
considère comme un traitement de texte 8 octets, mais le concept s'applique à d'autres tailles de mots.
Le rembourrage fonctionne en comblant les lacunes entre ces données pour vous assurer qu'ils sont alignés avec ces blocs, améliorant ainsi les performances lors de la lecture de la mémoire.
Toutefois, comme indiqué sur les réponses des autres, parfois l'espace est plus important alors la performance elle-même. Peut-être que vous traitez beaucoup de données sur un ordinateur qui n'a pas beaucoup de RAM (espace d'échange pourrait être utilisé, mais il est beaucoup plus lent). Vous pouvez organiser les variables du programme jusqu'à ce que le moins le rembourrage est fait (comme il a été grandement illustré dans d'autres réponses), mais si cela ne suffit pas, vous pouvez explicitement désactiver le rembourrage, qui est ce que l'emballage est.
Le remplissage de la structure de garnissage de la structure, le rembourrage utilisé lorsque des questions d'alignement plus, l'emballage utilisé lorsque les questions spatiales plus.
Certains compilateurs fournissent #pragma
pour supprimer le rembourrage ou pour le rendre emballé à un nombre n d'octets. Certains offrent des mots-clés pour ce faire. En général pragma qui est utilisée pour modifier la structure de rembourrage sera dans la forme ci-dessous (en fonction du compilateur):
#pragma pack(n)
Par exemple ARM fournit le mot-clé __packed
pour supprimer le rembourrage de la structure. Allez dans votre manuel compilateur pour en savoir plus à ce sujet.
Donc, une structure est une structure tassée sans rembourrage.
En général, les structures emballées seront utilisées
-
pour économiser de l'espace
-
pour mettre en forme une structure de données à transmettre sur le réseau en utilisant certains protocole (ce n'est pas une bonne pratique bien sûr parce que vous devez
traiter boutisme)
Rembourrage et l'emballage ne sont que deux aspects de la même chose:
- emballage ou l'alignement est la taille à laquelle chaque élément est arrondi
- rembourrage est l'espace supplémentaire ajouté pour correspondre à l'alignement
mystruct_A
, en supposant un alignement par défaut de 4, chaque élément est aligné sur un multiple de 4 octets. Étant donné que la taille de char
est 1, le rembourrage pour a
et c
est 4 - 1 = 3 octets tandis qu'aucun remplissage est requis pour int b
qui est déjà 4 octets. Il fonctionne de la même façon pour mystruct_B
.
emballage de la structure se fait que lorsque vous dites à votre compilateur explicitement pour emballer la structure. Rembourrage est ce que vous voyez. Le système 32 bits est rembourrage chaque champ pour l'alignement des mots. Si vous aviez dit à votre compilateur pour emballer les structures, ils seraient 6 et 5 octets, respectivement. Ne pas le faire bien. Il est un code non portable et fait compilateurs génèrent beaucoup plus lent (et parfois même poussette).
Il n'y a pas de mais à ce sujet! Qui veut saisir le sujet doit faire les suivants,
- L'art perdu de l'emballage de la structure écrit par Eric S. Raymond
- Coup d'œil sur l'exemple de code Eric
- Last but not least, ne pas oublier la règle suivante sur le remplissage que struct est aligné sur l'alignement le plus grand type exigences.
alignement de la structure de données est la manière dont les données est agencé et accéder à la mémoire de l'ordinateur. Il se compose de deux questions distinctes mais connexes: l'alignement des données et structure de données rembourrage . Quand un ordinateur moderne lit ou écrit à une adresse mémoire, il le fera en morceaux de taille mot (par exemple 4 morceaux d'octets sur un système 32 bits) ou plus. des moyens d'alignement de données mettant les données à une adresse de mémoire égale à un multiple de la taille de mot, ce qui augmente en raison de la façon dont les poignées de CPU de mémoire de la performance du système. Pour aligner les données, il peut être nécessaire d'insérer des octets vides de sens entre la fin de la dernière structure de données et le début de la prochaine, qui est le rembourrage de structure de données.
- Afin d'aligner les données dans la mémoire, un ou plusieurs octets vides (adresses) sont insérés (ou laissé vide) entre des adresses de mémoire qui sont attribuées à d'autres éléments de structure tout d'allocation de mémoire. Ce concept est appelé rembourrage de la structure.
- Architecture d'un processeur d'ordinateur est un moyen de telle sorte qu'il puisse lire 1 mot (4 octets dans le processeur de 32 bits) à partir de la mémoire à la fois.
- Pour utiliser cet avantage de processeur, les données sont toujours alignés en quatre octets de paquet qui conduit à insérer des adresses vides entre l'adresse d'un autre élément.
- En raison de ce concept de remplissage de la structure en C, la taille de la structure est pas toujours identique à ce que nous pensons.