Taille inexacte d'une structure avec des champs de bits dans mon livre

j'étudie les bases du langage C. Je suis arrivé au chapitre des structures avec des champs de bits. Le livre montre un exemple d'une structure avec deux types différents de données: divers bools et divers ints non signés.

le livre déclare que la structure a une taille de 16 bits et que sans l'utilisation de rembourrage la structure mesurerait 10 bits.

C'est la structure que le livre utilise dans l'exemple:

#include <stdio.h>
#include <stdbool.h>

struct test{

       bool opaque                 : 1;
       unsigned int fill_color     : 3;
       unsigned int                : 4;
       bool show_border            : 1;
       unsigned int border_color   : 3;
       unsigned int border_style   : 2;
       unsigned int                : 2;
};

int main(void)
{
       struct test Test;

       printf("%zun", sizeof(Test));

       return 0;
}

pourquoi sur mon compilateur à la place la même structure exacte mesure 16 octets (plutôt que bits) avec rembourrage et 16 octets sans capitonnage?

j'utilise

GCC (tdm-1) 4.9.2 compiler;
Code::Blocks as IDE.
Windows 7 64 Bit
Intel CPU 64 bit

C'est le résultat que j'obtiens:

enter image description here

<!-Voici une image de la page où l'exemple est:

enter image description here

23
demandé sur Boann 2018-04-26 13:53:12

7 réponses

le Microsoft ABI affiche les bitfields d'une manière différente de ce que GCC fait normalement sur les autres plateformes. Vous pouvez choisir D'utiliser la mise en page Compatible Microsoft avec le -mms-bitfields option, ou de le désactiver avec -mno-ms-bitfields. Il est probable que votre version GCC utilise -mms-bitfields par défaut.

selon la documentation, quand -mms-bitfields est activé:

  • chaque objet de données a une exigence d'alignement. L'exigence d'alignement pour tous les données, à l'exception des structures, des unions et des tableaux, sont soit la taille de l'objet, soit la taille de l'empaquetage actuel (spécifié avec l'attribut aligned ou le pack pragma), selon la moindre des deux. Pour les structures, les syndicats et les tableaux, l'exigence d'alignement est la plus importante exigence d'alignement de ses membres. Chaque objet est alloué un décalage de sorte que: décalage % alignment_requirement == 0

  • les champs de bits adjacents sont empaquetés dans la même allocation de 1, 2 ou 4 octets unité si les types intégraux sont de la même taille et si le champ bit suivant s'inscrit dans l'unité d'attribution actuelle sans dépasser la limite imposée par les exigences communes d'alignement des champs bit.

Depuis bool et unsigned int ont des tailles différentes, ils sont emballés et alignés séparément, ce qui augmente considérablement la taille de la structure. L'alignement de unsigned int est de 4 octets, et d'avoir à les réaligner trois fois dans le milieu de la structure conduit à une 16 octets taille totale.

Vous pouvez obtenir le même comportement de l'ouvrage en changeant boolunsigned int, ou en spécifiant -mno-ms-bitfields (bien que cela signifie que vous ne pouvez pas interférer avec le code compilé sur les compilateurs Microsoft).

notez que la norme C ne spécifie pas comment les bitfields sont disposés. Donc, ce que dit votre livre peut être vrai pour certaines plateformes, mais pas pour toutes.

27
répondu interjay 2018-04-26 18:38:26

considéré comme décrivant les dispositions de la norme linguistique C, votre texte fait des revendications injustifiées. Plus précisément, non seulement la norme dire que unsigned int est la structure de base de l'unité de structures de toute nature, il décline expressément toute obligation de la taille des unités de stockage dans laquelle champ de bits représentations sont stockés:

une implémentation peut attribuer n'importe quelle unité de stockage adressable grande assez pour tenir un peu- champ.

(C2011, 6.7.2.1 / 11)

le texte fait également des hypothèses sur le rembourrage qui ne sont pas supportées par la norme. Une implémentation C est libre d'inclure une quantité arbitraire de padding après tout ou partie des éléments et des unités de stockage bitfield d'un struct, y compris le dernier. Les implémentations utilisent généralement cette liberté pour aborder les considérations d'alignement des données, mais C ne limite pas le capitonnage à cette utilisation. C'est tout à fait séparé de la bitfields sans nom que votre texte appelle "padding".

je suppose que le livre devrait être félicité, cependant, pour éviter l'idée fausse malheureusement commune que C exige le type de données déclarées d'un champ bit pour avoir quoi que ce soit à voir avec la taille de l'unité(s) de stockage dans laquelle sa représentation réside. La norme ne fait pas une telle association.

pourquoi sur mon compilateur à la place la même structure mesure 16 octets (plutôt que des bits) avec du rembourrage et 16 octets sans rembourrage?

pour couper le texte autant que possible, il fait la distinction entre le nombre de bits de données occupés par les membres (16 bits au total, 6 appartenant à des bitfields non nommés) et la taille globale des instances du struct. Il semble affirmer que la structure globale sera de la taille d'un unsigned int, qui est apparemment 32 bits sur le système qu'il décrit, et ce serait la même chose pour les deux versions du struct.

en principe, vos tailles observées pourraient être expliquées par votre implémentation en utilisant une unité de stockage de 128 bits pour les bitfields. Dans la pratique, il utilise probablement une ou plusieurs unités de stockage plus petites, de sorte qu'une partie de l'espace supplémentaire dans chaque structure est attribuable au rembourrage fourni par la mise en œuvre, comme je l'ai mentionné ci-dessus.

il est très courant pour les implémentations C d'imposer une taille minimale à tous les types de structure, et donc de pad des représentations à cette taille quand nécessaire. Souvent, cette taille correspond à l'exigence d'alignement la plus stricte de tous les types de données pris en charge par le système, bien que ce soit, encore une fois, une considération de mise en œuvre, pas une exigence de la langue.

résultat: ce n'est qu'en se basant sur les détails de la mise en œuvre et / ou les extensions que vous pouvez prédire la taille exacte d'un struct, indépendamment de la présence ou de l'absence de membres bitfield.

8
répondu John Bollinger 2018-04-26 15:29:50

la norme C ne décrit pas tous les détails sur la façon dont les variables doivent être placées en mémoire. Cela laisse une marge d'optimisation qui dépend de la plate-forme utilisée.

pour vous donner une idée de la façon dont les choses sont localisées dans la mémoire, vous pouvez faire comme ceci:

#include <stdio.h>
#include <stdbool.h>

struct test{

       bool opaque                 : 1;
       unsigned int fill_color     : 3;
       unsigned int                : 4;
       bool show_border            : 1;
       unsigned int border_color   : 3;
       unsigned int border_style   : 2;
       unsigned int                : 2;
};


int main(void)
{
  struct test Test = {0};
  int i;
  printf("%zu\n", sizeof(Test));

  unsigned char* p;
  p = (unsigned char*)&Test;
  for(i=0; i<sizeof(Test); ++i)
  {
    printf("%02x", *p);
    ++p;
  }
  printf("\n");

  Test.opaque = true;

  p = (unsigned char*)&Test;
  for(i=0; i<sizeof(Test); ++i)
  {
    printf("%02x", *p);
    ++p;
  }
  printf("\n");

  Test.fill_color = 3;

  p = (unsigned char*)&Test;
  for(i=0; i<sizeof(Test); ++i)
  {
    printf("%02x", *p);
    ++p;
  }
  printf("\n");

  return 0;
}

en cours d'Exécution sur ce ideone (https://ideone.com/wbR5tI) j'obtiens:

4
00000000
01000000
07000000

Donc, je peux voir que opaque et fill_color sont tous les deux dans le premier octet. Fonctionnant exactement de la le même code sur une machine Windows (utilisant gcc) donne:

16
00000000000000000000000000000000
01000000000000000000000000000000
01000000030000000000000000000000

Donc ici, je peux voir que opaque et fill_color les deux dans le premier octet. Il semble que opaque prend 4 octets.

ceci explique que vous obtenez 16 octets au total, i.e. le bool prend 4 octets et 4 octets pour les champs entre les deux et après.

6
répondu 4386427 2018-04-26 11:55:46

à ma grande surprise, il semble y avoir une différence entre certains compilateurs en ligne GCC 4.9.2. Tout d'abord, voici mon code:

#include <stdio.h>
#include <stdbool.h>

struct test {
       bool opaque                 : 1;
       unsigned int fill_color     : 3;
       unsigned int                : 4;
       bool show_border            : 1;
       unsigned int border_color   : 3;
       unsigned int border_style   : 2;
       unsigned int                : 2;
};

struct test_packed {
       bool opaque                 : 1;
       unsigned int fill_color     : 3;
       unsigned int                : 4;
       bool show_border            : 1;
       unsigned int border_color   : 3;
       unsigned int border_style   : 2;
       unsigned int                : 2;
} __attribute__((packed));

int main(void)
{
       struct test padding;
       struct test_packed no_padding;

       printf("with padding: %zu bytes = %zu bits\n", sizeof(padding), sizeof(padding) * 8);
       printf("without padding: %zu bytes = %zu bits\n", sizeof(no_padding), sizeof(no_padding) * 8);

       return 0;
}

Et maintenant, les résultats de différents compilateurs.

GCC 4.9.2 de WandBox:

with padding: 4 bytes = 32 bits
without padding: 2 bytes = 16 bits

GCC 4.9.2 de http://cpp.sh/:

with padding: 4 bytes = 32 bits
without padding: 2 bytes = 16 bits

mais

CCG 4.9.2 à partir de theonlinecompiler.com:

with padding: 16 bytes = 128 bits
without padding: 16 bytes = 128 bits

(pour compiler correctement vous avez besoin de chagne %zu pour %u)

EDIT

@interjay réponse pourrait expliquer cela. Quand j'ai ajouté -mms-bitfields GCC 4.9.2 de WandBox, j'ai obtenu ceci:

with padding: 16 bytes = 128 bits
without padding: 16 bytes = 128 bits
6
répondu Grzegorz Adam Kowalski 2018-04-26 13:17:24

avant que l'auteur ne définisse la structure, il dit qu'il veut diviser les champs de bits en deux octets de façon à ce qu'il y ait un octet contenant les champs de bits pour les informations relatives au remplissage et un octet pour les informations relatives aux frontières.

Pour y parvenir, il ajoute (inserts) quelques les bits non utilisés (champ de bits):

unsigned int       4;  // padding of the first byte

il padde aussi le second octet, mais il n'y a pas besoin pour cela.

donc avant le rembourrage il y aurait 10 bits en usage et après le rembourrage il y a 16 bits définis (mais pas tous en cours d'utilisation).


Note: l'auteur utilise bool pour indiquer un champ 1/0. L'auteur suppose ensuite le _Bool le type C99 est aliasé à bool. Mais il semble compilateurs obtenir un peu confus ici. En remplaçant boolunsigned int résoudrait. À Partir De C99:

6.3.2: les termes suivants peuvent être utilisés dans une expression lorsqu'un int ou un int non signé peut être utilisé:

  • un bit-champ de type _Bool, int,signed int, ou unsigned int.
5
répondu Paul Ogilvie 2018-04-26 13:44:46

vous interprétez complètement mal ce que dit le livre.

il y a 16 bits de champs bit déclarés. 6 bits sont des champs sans nom qui ne peuvent pas être utilisés pour quoi que ce soit - c'est le rembourrage mentionné. 16 bits moins 6 bits égale 10 bits. Sans compter les champs de rembourrage, la structure a 10 bits utiles.

Combien d'octets de la structure a, dépend de la qualité du compilateur. Apparemment, vous avez rencontré un compilateur qui n'empaquette pas les bitfields de bool dans une struct, et il utilise 4 bytes pour un bool, un peu de mémoire pour les bitfields, plus le padding struct, total de 4 bytes, un autre de 4 bytes pour un bool, plus de mémoire pour les bitfields, plus le padding struct, total de 4 bytes, en ajoutant jusqu'à 16 bytes. C'est plutôt triste, vraiment. Cette structure pourrait raisonnablement être de deux octets.

2
répondu gnasher729 2018-04-26 23:41:22

il y a toujours eu deux façons courantes d'interpréter les types d'éléments bitfield:

  1. examiner si le type est signé ou non, mais ignorer les distinctions entre "char", "court", "int", etc. pour décider si un élément doit être placer.

  2. sauf si un bitfield est précédé d'un autre de même type, ou type signé/non signé correspondant, attribuer un objet de ce type et placez le bitfield à l'intérieur il. Lieu suivant bitfields avec le même le type de cet objet si elles correspondent.

je pense que la motivation derrière #2 était que sur une plate-forme où les valeurs de 16 bits doivent être alignées sur les mots, un compilateur donné quelque chose comme:

struct foo {
  char x;  // Not a bitfield
  someType f1 : 1;
  someType f2 : 7;
};

pourrait être capable d'allouer une structure à deux octets, les deux champs étant placés dans le second octet, mais si la structure avait été:

struct foo {
  char x;  // Not a bitfield
  someType f1 : 1;
  someType f2 : 15;
};

il serait nécessaire que tous f2 ajustement au sein d'un seul Mot de 16 bits, ce qui nécessiterait donc un octet de remplissage avant f1. En raison de la règle de séquence initiale commune, f1 doit être placé de façon identique dans ces deux structures, ce qui implique que si f1 pourrait satisfaire à la règle de séquence initiale commune, il faudrait le capitonner avant même dans la première structure.

comme il est, le code qui veut permettre la disposition plus dense dans le premier cas peut dire:

struct foo {
  char x;  // Not a bitfield
  unsigned char f1 : 1;
  unsigned char f2 : 7;
};

et invitez le compilateur à mettre les deux bitfields dans un octet immédiatement après x. Puisque le type est spécifié comme unsigned char, le compilateur n'a pas besoin de s'inquiéter de la possibilité d'un 15-champ de bits. Si la mise en page:

struct foo {
  char x;  // Not a bitfield
  unsigned short f1 : 1;
  unsigned short f2 : 7;
};

et l'intention était que f1 et f2 serait assis dans le même stockage, alors le compilateur devrait placer f1 d'une manière qui pourrait supporter un accès aligné sur un mot pour son "bunkmate" f2. Si le code:

struct foo {
  char x;  // Not a bitfield
  unsigned char f1 : 1;
  unsigned short f2 : 15;
};

f1 serait placé suivant x et f2 en un mot par lui-même.

notez que la norme C89 a ajouté une syntaxe pour forcer la mise en page qui empêche f1 d'être placé dans un byte avant le stockage utilisé f2:

struct foo {
  char x;  // Not a bitfield
  unsigned short : 0;  // Forces next bitfield to start at a "short" boundary
  unsigned short f1 : 1;
  unsigned short f2 : 15;
};

l'ajout de la syntaxe :0 en C89 élimine en grande partie la nécessité pour les compilateurs de considérer les types changeants comme des alignements de forçage, sauf lors du traitement de l'ancien code.

1
répondu supercat 2018-04-26 17:13:14