Partage
  • Partager sur Facebook
  • Partager sur Twitter

Bizarrerie avec une classe bit field

    2 août 2019 à 8:05:11

    Salut,

    le code suivant 

    #include <cstdint>
    #include <iostream>
    
    class GeneralUnit
    {
    	public:
    	
    		uint64_t                damage	: 50;
    		const uint8_t           idInfo	: 7;
    		uint8_t                 shield	: 7;
    };
    
    int main(void)
    {
    	std::cout << sizeof(GeneralUnit) << std::endl;
    	return 0;
    }

    renvoie 16 au lieu de 8. Cependant si je fais passer le premier membre de 50 à 49 bits, j'obtiens bien 8. Comment est-ce possible ?

    Merci à tous et bon vendredi :)

    -
    Edité par Law. 2 août 2019 à 8:05:43

    • Partager sur Facebook
    • Partager sur Twitter
      2 août 2019 à 11:04:44

      Salut !
      Ton topic m'a intéressé, j'ai fait quelques tests.
      D'abord, j'ai enlevé le const de ta variable idInfo, il me posait soucis, mais peu importe.
      Avec :
      class GeneralUnit
      {
      public:
      	uint64_t  damage : 50;
      	uint8_t	  idInfo : 7;
      	uint8_t   shield : 7;
      };

      j'obtiens comme toi.

      Un petit test :

      Je remplis la structure de 1 (FF) avec mon memset, puis je mets certaines variables à 1, et je vois que, en effet, mon nombre 1 pour damage a rempli les 50 premiers bits, suivis par 16 bits de padding, et que les uchar8_t on quand même utilisé tout leur octet, en remplissant 1 sur leurs 7 premiers bits, et le 1 restant est le padding, puis ensuite on arrive à 10 octets, il y a 6 octets de padding en plus.

      Mais la ou je ne te rejoins pas, c'est si je mets damage:16, j'obtiens ce que j'ai à droite : 2 octets utiles suivis de 6 octets de padding : mes uchar8 commencent à l'adresse 0x08.

      Ce qu'il faut noter, c'est que ton uint8_t n'économise que dalle : il consomme tout son octet, et a 1 bit de padding : la donnée suivante commence à l'octet suivant.

      Maintenant, si on change les uint8_t par des int :

      class GeneralUnit
      {
      public:
      	uint64_t  damage : 50;
      	int	  idInfo : 7;
      	int   shield : 7;
      };

      la on a bien idInfo et shield qui se suivent sans le bit de padding (séquence a partir de 0x8 : 81 C0, ce qui donne 1000 0001 1100 0000), les bits en gras sont les bits de padding de la suite, il y a une histoire de sens de lecture, bref)

      Je constate donc que la compression des structures doit être faite avec int var:taille et non autre chose que int.

      Si je mets :

      class GeneralUnit
      {
      public:
      	uint64_t  damage : 16;
      	int	  idInfo : 7;
      	int   shield : 7;
      };

      J'ai toujours une taille de 16 octets (j'ai bien mis 16 bits a damage), alors que si je mets int à la place de uint64_t, la j'ai bien size = 4 octets.

      Je pense que ça ne marche qu'avec des int. Et je ne sais donc pas s'il est possible d'utiliser des variables 64 bits dans ce cas.

      A noter que ces optimisations ont été conçues à une époque ou la mémoire coutait cher, et qu'à notre époque, quasi plus personne ne l'utilise. Et l'utilisation de variables 64 bits est plutôt contemporaine.


      • Partager sur Facebook
      • Partager sur Twitter

      Recueil de code C et C++  http://fvirtman.free.fr/recueil/index.html

        2 août 2019 à 11:21:19

        Fvirtman a écrit:

        A noter que ces optimisations ont été conçues à une époque ou la mémoire coutait cher, et qu'à notre époque, quasi plus personne ne l'utilise. Et l'utilisation de variables 64 bits est plutôt contemporaine.

        Ouais, enfin, j'en ai eu besoin pour traiter des trames réseaux, où donc la position des bits suit un schéma bien précis, et tout ce que me dis cppreference c'est que l'implémentation peut être absolument n'importe quoi ><

        The following properties of bit fields are implementation-defined
           The value that results from assigning or initializing a signed bit field with a value out of range, or from incrementing a signed bit field past its range.
        Everything about the actual allocation details of bit fields within the class object
          For example, on some platforms, bit fields don't straddle bytes, on others they do. Also, on some platforms, bit fields are packed left-to-right, on others right-to-left

        Et je m'étais aussi fait avoir par la padding parce que le compilo décide de faire de la pagination

        • Partager sur Facebook
        • Partager sur Twitter
        Dream on, Dream on, Dream until your dream comes true
          2 août 2019 à 11:48:10

          Les trames réseau, il me semble qu'on travaille quand même au niveau de l'octet ! Bien que quelques variables (codées sous 1 ou 2 octets, mais alignées) soient des bitfields, qu'on travaille a coup d'opérateurs << et | &, il me semble que le reste est aligné. On n'est pas dans le cas ici d'une variable de 7 bits qui commence au 4e bit du 3e octet !

          Pour ce cas la, je ne sais pas si c'est portable, mais tu peux mettre :

          #pragma pack(1)

          avant de déclarer ta structure : ça désactive l'alignement mémoire, mais au niveau des octets.

          Ainsi, sans ce pragma, un int suivi d'un char il y a 3 octets de perdus, avec la pragma, le char commence juste apres le int.

          • Partager sur Facebook
          • Partager sur Twitter

          Recueil de code C et C++  http://fvirtman.free.fr/recueil/index.html

            2 août 2019 à 12:01:09

            Je construis un simulateur qui a besoin de deux vecteurs d'instances de GeneralUnit, chaque vecteur a plusieurs dizaines de millions d'éléments, est supprimé et reconstruit régulièrement. Si je peux avoir une classe General Unit qui prend 1 octet plutôt que 3, je prends ! ^^

            Ensuite je ne comprends pas, si on ne peut pas faire de l'économie de bits autrement qu'avec int, pourquoi est-ce que j'arrive à tout faire tenir dans un seul octet si je mets 49 bits au membre uint64_t ?

            Merci Fvirtman pour tout le mal que tu t'es donné jusqu'à présent, mais je continue de m'interroger sur mon problème.

            • Partager sur Facebook
            • Partager sur Twitter
              2 août 2019 à 12:07:21

              Re,

              Sinon, tu ne mets qu'un seul int64 "d" dans ta classe et tu le travailles manuellement !

              Tu te fais une méthode GetDamage qui renvoie  "d&0x3FFFFFFFFFFFF"  (filtre 50 bits)

              Et tu fais un SetDamage, un Set/GetLesautres variables, et dedans, des &, des >> et des | pour les set.

              Et hop, tu as exactement ce que tu veux pour 8 octets.

              • Partager sur Facebook
              • Partager sur Twitter

              Recueil de code C et C++  http://fvirtman.free.fr/recueil/index.html

                2 août 2019 à 12:36:10

                Ce sont des fonctions qui seraient appelées très souvent, est-ce que ça n'alourdirait / ne ralentirait pas le programme s'il ne consiste qu'à modifier ces valeurs ou presque ?
                • Partager sur Facebook
                • Partager sur Twitter
                  2 août 2019 à 13:58:24

                  Les opérateurs bit a bit sont les plus rapide qui soient.

                  D'ailleurs, quand tu définis une structure avec le nombre de bits comme tu le fais, en interne c'est ce genre de chose qui est fait. Et en plus, tu perds les optimisations d'alignement (un int est fait pour avoir une adresse multiple de 4, quand on force autre chose, on risque de devoir le récupérer en deux fois).

                  Donc a mon avis, ce que je te propose est plus rapide.

                  Tu as vraiment besoin de 50 bits pour stocker tes nombres ?

                  • Partager sur Facebook
                  • Partager sur Twitter

                  Recueil de code C et C++  http://fvirtman.free.fr/recueil/index.html

                    2 août 2019 à 14:04:26

                    Fvirtman a écrit:

                    Les trames réseau, il me semble qu'on travaille quand même au niveau de l'octet ! Bien que quelques variables (codées sous 1 ou 2 octets, mais alignées) soient des bitfields, qu'on travaille a coup d'opérateurs << et | &, il me semble que le reste est aligné. On n'est pas dans le cas ici d'une variable de 7 bits qui commence au 4e bit du 3e octet !

                    Pour ce cas la, je ne sais pas si c'est portable, mais tu peux mettre :

                    #pragma pack(1)

                    avant de déclarer ta structure : ça désactive l'alignement mémoire, mais au niveau des octets.

                    Ainsi, sans ce pragma, un int suivi d'un char il y a 3 octets de perdus, avec la pragma, le char commence juste apres le int.

                    C'était un protocole CAN "maison", au niveau des données c'était par octet mais dans l'entête il y avait des champs 12 bits, des champs 3 bits ...

                    Je connaissais pas pragma pack, merci pour le tuyau. en fait je connais quasiment aucune directive pragma :euh:

                    Finalement je me suis arrangé pour que ça marche sur cette plateforme (un linux embarqué) et j'ai laissé des commentaires.

                    Je pense que la solution à base de masquage/décalage a un comportement plus maitrisé effectivement.

                    Law. a écrit:

                    Ce sont des fonctions qui seraient appelées très souvent, est-ce que ça n'alourdirait / ne ralentirait pas le programme s'il ne consiste qu'à modifier ces valeurs ou presque ?

                    légèrement, mais si c'est pour éviter le nid à emmerdes des bitfields, je le recommande

                    • Partager sur Facebook
                    • Partager sur Twitter
                    Dream on, Dream on, Dream until your dream comes true
                      2 août 2019 à 20:07:59

                      Salut,

                      Sans avoir été plus beaucoup plus loin que des tests bien basiques, avec Gcc (en 64 bits) sous windows, que j'utilises une structure proche de

                      struct A{
                          uint64_t bidule : 49;
                          uint8_t truc:7;
                          uint8_t brol:7;
                      };

                      une structure proche de

                      struct B{
                          uint64_t bidule : 50;
                          uint8_t truc:7;
                          uint8_t brol:7;
                      };

                      ou même une structure proche de

                      struct alignas(4) D{
                          uint64_t bidule : 49;
                          uint8_t truc : 7;
                          uint8_t brol : 7;
                      };

                      j'obtiens pour ma part, toujours la même réponse : alignof(A, B ou C) me renvoie forcément 8 quand sizeof(A, B ou C) me renvoie  16.


                      Cela semble indiquer que  l'alignement se fera d'office sur la taille du unit64_t

                      Il n'y a vraiment que quand on s'amuser à mettre le uint64_t entre le deux uint8_t que la taille (mais pas l'alignement :D) et qu'elle passe à 24.  Mais ca , c'est tout à fait normal ;)

                      Avec quel compilateur et / ou sur quel système obtiens tu arrives tu à obtenir une taille de 8 bits avec un bitset de 49 pour ton uint64_t ?

                      Ceci étant dit, si tu veux absolument une structure de 64 bits, tu peux aussi envisager de te tourner vers std::bitset sous une forme qui serait proche de

                      using GeneralUnit = std::bitset<64>;

                      la grosse question étant la manière dont on récupérera correctement les différentes valeurs (a savoir les donnée damage,idInfo et shield), mais bon, un simple décalage de bits devrait pouvoir faire l'affaire ;)

                      J'allais envisager de te parler de alignas, qui pourrait peut-être représenter une aide face à ton problème, mais comme mes propres essais n'étaient pas concluent... je crois que je vais m'en passer ;)

                      (Cependant, rien ne t'empêche bien sur de faire des essais avec ton propre compilateur à ce sujet ;) )

                      • Partager sur Facebook
                      • Partager sur Twitter
                      Ce qui se conçoit bien s'énonce clairement. Et les mots pour le dire viennent aisément.Mon nouveau livre : Coder efficacement - Bonnes pratiques et erreurs  à éviter (en C++)Avant de faire ce que tu ne pourras défaire, penses à tout ce que tu ne pourras plus faire une fois que tu l'auras fait
                        2 août 2019 à 21:48:20

                        Pour l'histoire de 8 ou 16 avec sizeof, je ne pense pas que se soit parce que ce n'est pas un int, mais parce que shield:7 dépasse la taille disponible dans le char de idInfo:7 qui ne contient plus qu'un bit et qu'un padding s'ajoute qui décale tous. Les bitfields ne permettent pas de superposer une valeur sur 2 types (sans le pragma pack, et probablement en fonction de l'implémentation).

                        Du coup, les bitfields, c'est rigolo, mais ce n'est pas une bonne idée de mélanger les types.

                        Ce serait beaucoup plus propre d'avoir une classe du style bitfields<50, 7, 7> qui permet lire et écrire au bon endroit.

                        • Partager sur Facebook
                        • Partager sur Twitter
                          2 août 2019 à 22:29:48

                          jo_link_noir a écrit:

                          Pour l'histoire de 8 ou 16 avec sizeof, je ne pense pas que se soit parce que ce n'est pas un int, mais parce que shield:7 dépasse la taille disponible dans le char de idInfo:7 qui ne contient plus qu'un bit et qu'un padding s'ajoute qui décale tous. Les bitfields ne permettent pas de superposer une valeur sur 2 types (sans le pragma pack, et probablement en fonction de l'implémentation).

                          Du coup, les bitfields, c'est rigolo, mais ce n'est pas une bonne idée de mélanger les types.

                          Ce serait beaucoup plus propre d'avoir une classe du style bitfields<50, 7, 7> qui permet lire et écrire au bon endroit.


                          De fait, toujours avec le même compilateur, et sous le même OS, chez moi, une structure de bitfields  dont tous sont déclarés comme étant des uint64_t, sous la forme de
                          struct  C{
                              uint64_t bidule : 50;
                              uint64_t truc : 7;
                              uint64_t brol : 7;
                          };

                          me donne une taille de 8 (sans pragma(pack) et sans jouer avec alignas ou autres) :D

                          EDIT : Mieux encore, cela fournit le même résultat (sous windows, toujours) avec clang (9.0) et visual studio (2017) :D

                          -
                          Edité par koala01 2 août 2019 à 22:35:47

                          • Partager sur Facebook
                          • Partager sur Twitter
                          Ce qui se conçoit bien s'énonce clairement. Et les mots pour le dire viennent aisément.Mon nouveau livre : Coder efficacement - Bonnes pratiques et erreurs  à éviter (en C++)Avant de faire ce que tu ne pourras défaire, penses à tout ce que tu ne pourras plus faire une fois que tu l'auras fait

                          Bizarrerie avec une classe bit field

                          × Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
                          × Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
                          • Editeur
                          • Markdown