web-dev-qa-db-fra.com

Pourquoi l'affectation d'une valeur à un champ de bits ne renvoie-t-elle pas la même valeur?

J'ai vu le code ci-dessous dans ce post Quora :

#include <stdio.h>

struct mystruct { int enabled:1; };
int main()
{
  struct mystruct s;
  s.enabled = 1;
  if(s.enabled == 1)
    printf("Is enabled\n"); // --> we think this to be printed
  else
    printf("Is disabled !!\n");
}

En C & C++, la sortie du code est inattendue ,

Est désactivé !!

Bien que l'explication relative au "bit de signe" soit donnée dans cet article, je suis incapable de comprendre comment il est possible que nous fixions quelque chose et que cela ne reflète pas ce qu'il est.

Quelqu'un peut-il donner une explication plus élaborée?


Remarque : Les deux balises c & c ++ sont obligatoires, car leurs normes diffèrent légèrement par leur description. les champs de bits. Voir les réponses pour spécification C et spécification C++ .

95
iammilind

Conformément à norme C++ n471 , un extrait de code très similaire est fourni. Le type utilisé est BOOL (personnalisé), mais il peut s'appliquer à tout type.

12.2.4

4 Si la valeur true ou false est stockée dans un champ de bits de type bool de taille quelconque (y compris un champ de bits à un bit), la valeur initiale bool et la valeur du champ de bits doivent comparer égaux. Si la valeur d'un énumérateur est stockée dans un champ de bits du même type d'énumération et que le nombre de bits dans le champ de bits est suffisamment grand pour contenir toutes les valeurs de ce type d'énumération (10.2), l'original la valeur de l'énumérateur et la valeur du champ de bits doivent être comparées égales]. [ Exemple:

enum BOOL { FALSE=0, TRUE=1 };
struct A {
  BOOL b:1;
};
A a;
void f() {
  a.b = TRUE;
  if (a.b == TRUE)    // yields true
    { /* ... */ }
}

- fin exemple]


Au premier regard, la partie audacieuse semble ouverte à l'interprétation. Cependant, l'intention correcte devient claire lorsque le enum BOOL est dérivé de la int.

enum BOOL : int { FALSE=0, TRUE=1 }; // ***this line
struct mystruct { BOOL enabled:1; };
int main()
{
  struct mystruct s;
  s.enabled = TRUE;
  if(s.enabled == TRUE)
    printf("Is enabled\n"); // --> we think this to be printed
  else
    printf("Is disabled !!\n");
}

Avec le code ci-dessus, il donne un avertissement sans -Wall -pedantic:

avertissement: "mystruct :: enabled" est trop petit pour contenir toutes les valeurs de "enum BOOL" struct mystruct { BOOL enabled:1; };

La sortie est:

Est désactivé !! (en utilisant enum BOOL : int)

Si enum BOOL : int est simplifié enum BOOL, alors le résultat obtenu est tel que spécifié ci-dessus:

Est activé (en utilisant enum BOOL)


Par conséquent, on peut en conclure, comme peu d’autres réponses l’ont fait, que le type int n’est pas assez grand pour stocker la valeur "1" dans un seul champ de bits.

8
iammilind

Les champs de bits sont incroyablement mal définis par la norme. Étant donné ce code struct mystruct {int enabled:1;};, alors nous ne savons pas savons:

  • Combien cela occupe-t-il - s’il existe des bits/octets de remplissage et leur emplacement en mémoire.
  • Où le bit est situé dans la mémoire. Non défini et dépend également de endianess.
  • Indique si un champ de bits int:n doit être considéré comme signé ou non signé.

En ce qui concerne la dernière partie, C17 6.7.2.1/10 dit:

Un champ de bits est interprété comme ayant un type entier signé ou non signé composé du nombre de bits spécifié 125)

Note non normative expliquant ce qui précède:

125) Comme spécifié au 6.7.2 ci-dessus, si le spécificateur de type utilisé est int ou un nom de type défini comme int, il est défini par l'implémentation que le champ de bits soit signé ou non.

Si le champ binaire doit être considéré comme signed int et que vous créez un peu de taille 1, il n'y a plus de place pour les données, mais uniquement pour le bit de signe. C'est la raison pour laquelle votre programme peut donner des résultats étranges sur certains compilateurs.

Bonnes pratiques:

  • N'utilisez jamais de champs de bits pour quelque raison que ce soit.
  • Évitez d'utiliser le type signé int pour toute forme de manipulation de bits.
76
Lundin

Je suis incapable de comprendre, comment est-il possible que nous fixions quelque chose et que cela ne se présente pas tel quel.

Demandez-vous pourquoi il compile vs vous donne une erreur?

Oui, cela devrait idéalement vous donner une erreur. Et c'est le cas si vous utilisez les avertissements de votre compilateur. Dans GCC, avec -Werror -Wall -pedantic:

main.cpp: In function 'int main()':
main.cpp:7:15: error: overflow in conversion from 'int' to 'signed char:1' 
changes value from '1' to '-1' [-Werror=overflow]
   s.enabled = 1;
           ^

La raison pour laquelle cela reste jusqu'à être défini par l'implémentation par opposition à une erreur peut avoir plus à voir avec les utilisations historiques, où exiger un transtypage signifierait briser l'ancien code. Les auteurs de la norme peuvent penser que les avertissements ont suffi à prendre le relais pour les personnes concernées.

Pour ajouter un peu de prescriptivisme, je ferai écho à la déclaration de @ Lundin: "N'utilisez jamais de champs de bits pour quelque raison que ce soit." Si vous avez le genre de bonnes raisons pour obtenir des informations de bas niveau et spécifiques sur votre mémoire des détails de mise en page qui vous feraient penser que vous aviez besoin de champs de bits en premier lieu, les autres exigences associées que vous avez presque certainement se heurteront à leur sous-spécification.

(TL; DR - Si vous êtes assez sophistiqué pour légitimement "avoir besoin" de champs de bits, ils ne sont pas assez bien définis pour vous servir.)

57
HostileFork

C'est le comportement défini par l'implémentation. Je pars du principe que les machines sur lesquelles vous utilisez ceci utilisent des entiers signés à deux compléments et traitent int dans ce cas comme un entier signé pour expliquer pourquoi vous n'entrez pas si vrai dans l'instruction if.

struct mystruct { int enabled:1; };

déclare enable sous la forme d'un champ de bits à 1 bit. Comme il est signé, les valeurs valides sont -1 et 0. La définition du champ sur 1 déborde ce bit qui revient à -1 (il s'agit d'un comportement non défini).

Essentiellement, lorsqu'il s'agit d'un champ de bits signé, la valeur maximale est 2^(bits - 1) - 1 qui est 0 dans ce cas.

22
NathanOliver

Vous pourriez penser que, dans le système complémentaire du complément à 2, le bit le plus à gauche est le bit de signe. Tout entier signé avec le bit le plus à gauche est donc une valeur négative.

Si vous avez un entier signé sur 1 bit, il n’a que le bit de signature. Ainsi, l’attribution de 1 à ce bit ne peut définir que le bit de signe. Ainsi, lors de la relecture, la valeur est interprétée comme négative, de même que -1.

Les valeurs qu'un entier signé de 1 bit peut contenir sont -2^(n-1)= -2^(1-1)= -2^0= -1 et 2^n-1= 2^1-1=0

10
Paul Ogilvie

Il n'y a rien de mal à ce que vous compreniez les champs de bits. Ce que je vois, c'est que vous avez d'abord redéfini mystruct en tant que struct mystruct {int enabled: 1;}, puis en tant que struct mystruct s;. Ce que vous auriez dû coder était:

#include <stdio.h>

struct mystruct { int enabled:1; };
int main()
{
    mystruct s; <-- Get rid of "struct" type declaration
    s.enabled = 1;
    if(s.enabled == 1)
        printf("Is enabled\n"); // --> we think this to be printed
    else
        printf("Is disabled !!\n");
}
0
ar18