# Bitflags et Enums définis – coexistence pacifique en “c”

Je viens de découvrir la joie des bitflags. J’ai plusieurs questions sur les “meilleures pratiques” concernant l’utilisation de bitflags en C. J’ai tout appris à partir de divers exemples trouvés sur le Web, mais j’ai toujours des questions.

Afin de gagner de la place, j’utilise un seul champ entier 32 bits dans une structure ( A->flag ) pour représenter plusieurs ensembles différents de propriétés booléennes. En tout, 20 bits différents sont #define d. Certains d’entre eux sont vraiment des indicateurs de présence / absence (STORAGE-INTERNAL ou STORAGE-EXTERNAL). D’autres ont plus de deux valeurs (par exemple, un ensemble de formats mutuellement exclusifs: FORMAT-A, FORMAT-B, FORMAT-C). J’ai défini des macros pour la définition de bits spécifiques (et la désactivation simultanée de bits mutuellement exclusifs). J’ai également défini des macros pour tester si une combinaison spécifique de bits est définie dans l’indicateur.

Cependant, ce qui est perdu dans l’approche ci-dessus est le groupe spécifique de drapeaux qui est le mieux capturé par les enums. Pour l’écriture de fonctions, j’aimerais utiliser des énumérations (par exemple, STORAGE-TYPE et FORMAT-TYPE), afin que les définitions de fonctions aient l’air sympa. Je m’attends à utiliser des énumérations uniquement pour transmettre des parameters et des macros #defined pour définir et tester des indicateurs.

  1. (a) Comment définir flag ( A->flag ) comme un entier 32 bits de manière portable (sur des plates-formes 32 bits / 64 bits)?

  2. (b) Devrais-je m’inquiéter des différences de taille potentielles dans la façon dont A->flag vs #define d constantes vs enums sont stockés?

  3. (c) Suis-je en train de rendre les choses inutilement compliquées, c’est-à-dire devrais-je m’en tenir à utiliser des constantes #define d pour passer des parameters en tant int ordinaires? De quoi d’autre dois-je m’inquiéter dans tout cela?

Je m’excuse pour la question mal articulée. Cela reflète mon ignorance des problèmes potentiels.

Comme d’autres l’ont déjà dit, votre problème (a) peut être résolu en utilisant et uint32_t ou uint32_t (si vous souhaitez vous soucier des macro-systèmes de Burroughs qui contiennent des mots de 36 bits). Notez que MSVC ne prend pas en charge C99, mais @DigitalRoss indique où vous pouvez obtenir un en-tête approprié à utiliser avec MSVC.

Votre problème (b) n’est pas un problème; C va taper convert en toute sécurité pour vous si cela est nécessaire, mais ce n’est probablement même pas nécessaire.

Le domaine le plus préoccupant est (c) et en particulier le sous-champ format. Là, 3 valeurs sont valides. Vous pouvez gérer cela en allouant 3 bits et en exigeant que le champ de 3 bits soit l’une des valeurs 1, 2 ou 4 (toute autre valeur est non valide en raison d’un nombre trop élevé ou insuffisant de bits). Vous pouvez également atsortingbuer un nombre à 2 bits et spécifier que 0 ou 3 (ou, si vous le souhaitez vraiment, 1 ou 2) est invalide. La première approche utilise un bit de plus (ce n’est pas un problème pour l’instant puisque vous n’utilisez que 20 bits sur 32), mais il s’agit d’une approche purement bitflag.

Lors de l’écriture d’appels de fonction, il n’y a pas de problème particulier d’écriture:

 some_function(FORMAT_A | STORAGE_INTERNAL, ...); 

Cela fonctionnera que FORMAT_A soit un #define ou un enum (tant que vous spécifiez correctement la valeur enum). Le code appelé doit vérifier si l’appelant a eu un problème de concentration et a écrit:

 some_function(FORMAT_A | FORMAT_B, ...); 

Mais il s’agit d’une vérification interne pour le module, mais pas d’une vérification pour les utilisateurs du module.

Si les utilisateurs doivent changer beaucoup de bits dans le membre flags , les macros permettant de définir et de désactiver le champ de format peuvent être utiles. Certains pourraient dire que les champs booléens purs en ont à peine besoin (cependant, et je sympathise avec vous). Il peut être préférable de traiter le membre flags comme étant opaque et de fournir des «fonctions» (ou macros) pour obtenir ou définir tous les champs. Moins les gens peuvent se tromper, moins ils se tromperont.

Déterminez si l’utilisation de champs de bits fonctionne pour vous. D’après mon expérience, ils conduisent à un gros code et pas nécessairement à un code très efficace; YMMV.

Hmmm… rien de très définitif ici, pour l’instant.

  • J’utiliserais des énumérations pour tout car elles sont garanties d’être visibles dans un débogueur où les valeurs #define ne le sont pas.
  • Je ne fournirais probablement pas de macros pour obtenir ou définir des bits, mais je suis parfois une personne cruelle.
  • Je fournirais des conseils sur la façon de définir la partie format du champ flags, et pourrais fournir une macro pour le faire.

Comme ceci, peut-être:

 enum { ..., FORMAT_A = 0x0010, FORMAT_B = 0x0020, FORMAT_C = 0x0040, ... }; enum { FORMAT_MASK = FORMAT_A | FORMAT_B | FORMAT_C }; #define SET_FORMAT(flag, newval) (((flag) & ~FORMAT_MASK) | (newval)) #define GET_FORMAT(flag) ((flag) & FORMAT_MASK) 

SET_FORMAT est sûr s’il est utilisé avec précision, mais horrible s’il est maltraité. Un des avantages des macros est que vous pouvez les remplacer par une fonction qui valide les choses de manière approfondie si nécessaire. cela fonctionne bien si les personnes utilisent les macros de manière cohérente.

Il existe un en-tête C99 destiné à résoudre le problème exact (a), mais pour une raison quelconque, Microsoft ne l’implémente pas. Heureusement, vous pouvez obtenir pour Microsoft Windows ici . Toutes les autres plates-formes l’auront déjà. Les types int de 32 bits sont uint32_t et int32_t . Ceux-ci existent également en versions 8, 16 et 64 bits.

Donc, cela prend soin de (a).

(b) et (c) sont un peu la même question. Nous faisons des suppositions chaque fois que nous développons quelque chose. Vous supposez que C sera disponible. Vous supposez que peut être trouvé quelque part. Vous pouvez toujours supposer que int faisait au moins 16 bits et maintenant, une hypothèse> = 32 bits est relativement raisonnable.

En général, vous devriez essayer d’écrire des programmes conformes qui ne dépendent pas de la présentation, mais ils émettront des hypothèses sur la longueur des mots. Vous devriez vous préoccuper de la performance au niveau de l’algorithme, c’est-à-dire, est-ce que j’écris quelque chose qui est quadratique, polynomiale, exponentielle?

Vous ne devez pas vous préoccuper des performances au niveau de l’opération jusqu’à ce que (a) vous remarquiez un décalage des performances et (b) que vous ayez profilé votre programme. Vous devez faire votre travail sans perdre votre temps à vous inquiéter des opérations individuelles. 🙂

Oh, je devrais append que vous n’avez pas particulièrement besoin de vous inquiéter des performances de bas niveau lorsque vous écrivez le programme en C en premier lieu. C est le langage le plus proche du métal qui va le plus vite possible. Nous écrivons régulièrement des choses en php, python, ruby ​​ou lisp parce que nous voulons un langage puissant et que les processeurs sont si rapides de nos jours que nous pouvons nous en sortir avec un interprète entier, sans parler du choix pas parfait du mot-clé -longueur ops. 🙂

Vous pouvez utiliser des champs de bits et laisser le compilateur faire le tourbillon. Par exemple:

 struct PropertySet { unsigned internal_storage : 1; unsigned format : 4; }; int main(void) { struct PropertySet x; struct PropertySet y[10]; /* array of structures containing bit-fields */ if (x.internal_storage) x.format |= 2; if (y[2].internal_storage) y[2].format |= 2; return 0; } 

Édité pour append un tableau de structures

Pour la question a, si vous utilisez C99 (vous l’utilisez probablement), vous pouvez utiliser le type prédéfini uint32_t (ou, s’il n’est pas prédéfini, il peut être trouvé dans le fichier d’en-tête stdint.h ).

Concernant (c): si vos énumérations sont définies correctement, vous devriez pouvoir les passer comme arguments sans problème. Quelques points à considérer:

  1. Le stockage d’énumération dépend souvent du compilateur. Par conséquent, en fonction du type de développement que vous effectuez (vous ne précisez pas s’il s’agit de Windows, de Linux, de Linux intégré, etc.), vous pouvez consulter les options du compilateur pour le stockage enum. assurez-vous qu’il n’y a pas de problèmes là-bas. Je suis généralement d’accord avec le consensus ci-dessus selon lequel le compilateur doit lancer vos énumérations de manière appropriée – mais il faut en être conscient.
  2. Dans le cas où vous effectuez un travail intégré, de nombreux programmes de contrôle de la qualité statique tels que PC Lint “aboient” si vous commencez à être trop malin avec les énumérations, les définitions # et les champs de bits. Si vous faites un développement qui doit passer par n’importe quelle qualité, cela peut être quelque chose à garder à l’esprit. En fait, certaines normes automobiles (telles que MISRA-C) deviennent carrément irritables si vous essayez d’obtenir sortingg avec des champs de bits.
  3. “Je viens de découvrir la joie des bitflags.” Je suis d’accord avec vous – je les trouve très utiles.

J’ai ajouté des commentaires à chaque réponse ci-dessus. Je pense avoir une certaine clarté. Il semble que les enums soient plus propres car ils apparaissent dans le débogueur et gardent les champs séparés. les macros peuvent être utilisées pour définir et obtenir des valeurs.

J’ai également lu que les énumérations sont stockées sous forme de petits nombres entiers – ce qui, si j’ai bien compris, ne pose pas de problème pour les tests booléens, car ceux-ci seraient perforés à partir de la plupart des bits de droite. Mais, les enums peuvent-ils être utilisés pour stocker de grands nombres entiers (1 << 21) ??

merci encore à vous tous. J’ai déjà appris plus qu’il y a deux jours !!

~ Russ