web-dev-qa-db-fra.com

Rembourrage dans les structures en C

Ceci est une question d'entrevue. Jusqu'à présent, je pensais que ces questions étaient purement dépendantes du compilateur et ne devraient pas m'inquiéter, mais maintenant, je suis plutôt curieux à ce sujet.

Supposons que l'on vous donne deux structures:

struct A {  
  int* a;  
  char b;  
 }  

et ,

struct B {  
  char a;  
  int* b;  
}  

Alors lequel préférez-vous et pourquoi? Ma réponse a été comme ceci (bien que je tirais un peu dans le noir) que la première structure devrait être préférée car le compilateur alloue de l'espace pour une structure en quelques multiples de la taille de Word (qui est la taille du pointeur - 4 octets sur 32 machines à bits et 8 octets sur celles à 64 bits). Ainsi, pour les deux structures, le compilateur allouerait 8 octets (en supposant que c'est une machine 32 bits). Mais, dans le premier cas, le remplissage serait effectué après toutes mes variables (c'est-à-dire après a et b). Donc, même si par hasard, b obtient une valeur qui déborde et détruit mes prochains octets remplis, mais mon a est toujours sûr.

Il ne semblait pas très satisfait et a demandé un inconvénient de la première structure par rapport à la seconde. Je n'avais pas grand-chose à dire. :RÉ

Veuillez m'aider avec les réponses.

40
letsc

Je ne pense pas qu'il y ait un avantage pour aucune de ces structures. Il y a une (!) Constante dans cette équation. L'ordre des membres de la structure est garanti comme déclaré.

Donc, dans le cas comme celui-ci, la deuxième structure pourrait a un avantage, car elle a probablement une taille plus petite, mais pas dans votre exemple, car elle aura probablement la même taille:

struct {
    char a;
    int b;
    char c;
} X;

Vs.

struct {
    char a;
    char b;
    int c;
} Y;

n peu plus d'explications concernant les commentaires ci-dessous:

Tout ce qui suit n'est pas à 100%, mais la façon courante dont les structures seront construites dans un système 32 bits où int est de 32 bits:

Struct X:

|     |     |     |     |     |     |     |     |     |     |     |     |
 char  pad    pad   pad   ---------int---------- char   pad   pad   pad   = 12 bytes

struct Y:

|     |     |     |     |     |     |     |     |
 char  char  pad   pad   ---------int----------        = 8 bytes
34
MByD

Certaines machines accéder plus efficacement aux données lorsque les valeurs sont alignées sur une limite. Certaines nécessitent données à aligner.

Sur les machines 32 bits modernes comme le SPARC ou Intel [34] 86, ou toute puce Motorola à partir du 68020, chaque iten de données doit généralement être `` auto-aligné '', en commençant sur une adresse qui est un multiple de sa taille de type. Ainsi, les types 32 bits doivent commencer sur une frontière 32 bits, les types 16 bits sur une frontière 16 bits, 8 bits les types peuvent commencer n'importe où , les types struct/array/union ont l'alignement de leur membre le plus restrictif.

Vous pourriez donc avoir

struct B {  
    char a;
    /* 3 bytes of padding ? More ? */
    int* b;
}

Une règle simple qui minimise le remplissage dans le cas `` auto-aligné '' (et ne fait aucun mal dans la plupart des autres) consiste à ordonner vos membres de structure en diminuant la taille.

Personnellement, je ne vois pas d'inconvénient avec la première structure par rapport à la seconde.

11
cnicutar

Je ne peux pas penser à un inconvénient de la première structure par rapport à la seconde dans ce cas particulier, mais il est possible de trouver des exemples où il y a des inconvénients à la règle générale de mettre les plus grands membres en premier:

struct A {  
    int* a;
    short b;
    A(short num) : b(2*num+1), a(new int[b]) {} 
    // OOPS, `b` is used uninitialized, and a good compiler will warn. 
    // The only way to get `b` initialized before `a` is to declare 
    // it first in the class, or of course we could repeat `2*num+1`.
}

J'ai également entendu parler d'un cas assez compliqué pour les grandes structures, où le processeur a des modes d'adressage rapides pour accéder au pointeur + décalage, pour les petites valeurs de décalage (jusqu'à 8 bits, par exemple, ou une autre limite d'une valeur immédiate). Il est préférable de micro-optimiser une grande structure en plaçant autant de champs les plus couramment utilisés que possible à portée des instructions les plus rapides.

Le processeur peut même avoir un adressage rapide pour le pointeur + décalage et le pointeur + 4 * décalage. Supposons ensuite que vous ayez 64 champs char et 64 champs int: si vous placez d'abord les champs char, alors tous les champs des deux types peuvent être adressés en utilisant les meilleures instructions, alors que si vous placez d'abord les champs int puis les champs char qui ne le sont pas 4 -aligned devra simplement être accessible différemment, peut-être en chargeant une constante dans un registre plutôt qu'avec une valeur immédiate, car ils sont en dehors de la limite de 256 octets.

Je n'ai jamais eu à le faire moi-même, et par exemple x86 autorise de toute façon de grandes valeurs immédiates. Ce n'est pas le genre d'optimisation auquel tout le monde pense normalement à moins de passer beaucoup de temps à regarder Assembly.

4
Steve Jessop

En bref, il n'y a aucun avantage à choisir non plus dans le cas général. La seule situation où le choix serait important dans la pratique est si le remplissage de la structure est activé, dans le cas struct A serait un meilleur choix (car les deux champs seraient alignés en mémoire, tandis qu'en struct B le champ b serait situé à un décalage impair). L'emballage de la structure signifie qu'aucun octet de remplissage n'est inséré à l'intérieur de la structure.

Cependant, il s'agit d'un scénario plutôt rare: le remplissage de structure n'est généralement activé que dans des situations spécifiques. Ce n'est pas une préoccupation pour la plupart des programmes. Et il n'est pas non plus contrôlable par aucune construction portable dans la norme C.

2
alecov

C'est aussi une supposition, mais la plupart des compilateurs ont une option de désalignement qui n'ajoutera explicitement pas d'octets de remplissage. Cela nécessite alors (sur certaines plates-formes) une correction d'exécution (interruption matérielle) pour aligner les accès à la volée (avec une pénalité de performance correspondante). Si je me souviens bien, HPUX est tombé dans cette catégorie. Ainsi, la première structure, les champs sont toujours alignés même lorsque des options de compilateur mal alignées sont utilisées (car comme vous l'avez dit, le remplissage serait à la fin).

1
Kevin