Champ de bits de manipulation en C
Le problème classique de test et de configuration de bits en un nombre entier dans C est peut-être le plus commun de niveau intermédiaire compétences en programmation. Vous jeu et le tester avec de simples masques de bits comme
unsigned int mask = 1<<11;
if (value & mask) {....} //Test for the bit
value |= mask; //set the bit
value &= ~mask; //clear the bit
Un intéressant post de blog soutient que c'est source d'erreurs, difficile à maintenir, et des mauvaises pratiques. Le langage C fournit elle-même peu d'accès au niveau de ce qui est typesafe et portable:
typedef unsigned int boolean_t;
#define FALSE 0
#define TRUE !FALSE
typedef union {
struct {
boolean_t user:1;
boolean_t zero:1;
boolean_t force:1;
int :28; /* unused */
boolean_t compat:1; /* bit 31 */
};
int raw;
} flags_t;
int
create_object(flags_t flags)
{
boolean_t is_compat = flags.compat;
if (is_compat)
flags.force = FALSE;
if (flags.force) {
[...]
}
[...]
}
Mais cela me fait grincer des dents.
L'argument intéressant ma collègue de travail et j'ai eu à ce sujet est encore en suspens. Les deux styles de travail, et je maintiens le classique masque méthode est facile, sûr et clair. Mon collègue est d'accord, il est courant et facile, mais le champ de bits de l'union méthode vaut le supplément de quelques lignes pour le rendre portable et plus sûr.
Est-il des arguments en plus pour chaque côté? En particulier est-il un échec possible, peut-être avec l'endianness, que le masque de bits méthode peut manquer, mais où la structure de la méthode est sans danger?
- Je pense que vous avez droit à grincer des dents.
- Comment
flags.compat
etflags.force
travail? Ne devraient-ils pas êtreflags.{structure_name}.compat
etflags.{structure_name}.force
? - vous pouvez vous référer à la membres non struct/union directement dans gcc GNU extension, et il est maintenant en C11 standard.
Vous devez vous connecter pour publier un commentaire.
Bitfields ne sont pas tout à fait aussi portable que vous en pensez, en tant que "C ne permet pas de garantir l'ordre des champs à l'intérieur de la machine de mots" (Le C-livre)
Ignorer que, utilisé correctement, soit la méthode est sûre. Les deux méthodes permettent aussi symbolique de l'accès à l'intégrale des variables. On peut faire valoir que la méthode de champ de bits est plus facile à écrire, mais cela signifie aussi plus de code pour examen.
flags.compat
etflags.force
travail? Ne devraient-ils pas êtreflags.{structure_name}.compat
etflags.{structure_name}.force
?Si le problème est que l'établissement et la compensation de bits est sujette aux erreurs, puis la bonne chose à faire est d'écrire des fonctions ou macros assurez-vous que vous le faites à droite.
Qui rend votre code lisible si vous n'avez pas l'esprit que val a à être une lvalue, sauf pour BIT_IS_SET. Si cela ne vous rend pas heureux, alors vous prenez affectation, parenthesize et de les utiliser comme val = SET_BIT(val, someIndex); qui sera l'équivalent.
Vraiment, la réponse est à considérer découplage de la ce que vous voulez de la façon dont vous voulez le faire.
bitIndex
macro argument de la macro-expansion:#define SET_BIT(val, bitIndex) val |= (1 << (bitIndex))
pour éviter d'éventuels priorité des questions si l'argument est une expression.Bitfields sont grand et facile à lire, mais malheureusement le langage C ne permet pas de spécifier la mise en page de bitfields dans la mémoire, ce qui signifie qu'ils sont essentiellement inutiles pour traiter les paniers de données dans les formats de disque ou binaire fil des protocoles. Si vous me demandez, cette décision était une erreur de conception dans C—Ritchie auraient pu choisir une commande et coincé avec elle.
Vous avez à penser à ce sujet dans le point de vue d'un écrivain -- connaître votre public. Donc, il ya un couple de "publics" à prendre en compte.
D'abord il y a le classique programmeur C, qui ont bitmasked toute leur vie et pourrait le faire dans leur sommeil.
Deuxième il y a le newb, qui n'a aucune idée de ce que tout cela,|, & truc est. Ils ont été de programmation php à leur travail, et maintenant ils travaillent pour vous. (Je dis cela comme un newb qui n'php)
Si vous écrivez pour satisfaire à la première audience (c'est-masque-tous-journée), vous allez les rendre très heureux, et ils vont être en mesure de maintenir le code, les yeux bandés. Cependant, le newb aurez probablement besoin de surmonter une grande courbe d'apprentissage avant qu'ils ne soient en mesure de maintenir votre code. Ils auront besoin d'apprendre sur les opérateurs binaires, la façon dont vous utilisez ces opérations à définir/effacer les bits, etc. Vous êtes presque certainement allez avoir des bugs introduits par la newb comme il/elle toutes les astuces nécessaires pour obtenir que cela fonctionne.
D'autre part, si vous écrivez à satisfaire à la seconde audience, les newbs aurez un temps plus facile de maintenir le code. Ils ont un temps plus facile de groking
que
et la première audience aura juste grognon, mais il est difficile d'imaginer qu'ils ne serait pas en mesure d'analyser et de maintenir la nouvelle syntaxe. C'est juste beaucoup plus difficile à visser. Il n'y aura pas de nouveaux bugs, parce que le newb sera plus facile de maintenir le code. Il vous suffit de faire des conférences sur comment "de retour dans ma journée vous avez besoin d'une main ferme et d'une aiguille magnétisée pour définir les bits... nous n'avons même pas de masques de bits!" (merci XKCD).
Donc, je voudrais vous recommandons vivement d'utiliser les champs sur les masques de bits de newb-sûr de votre code.
L'union d'utilisation a un comportement non défini selon la norme ANSI C la norme, et donc, ne doit pas être utilisé (ou au moins ne pas être considéré comme portable).
De la ISO/IEC 9899:1999 (C99) standard:
Annexe J - Problèmes De Portabilité:
6.2.6.1 - Concepts de Langage de Représentation des Types Généraux:
Donc, si vous voulez garder le champ de bits ↔ entier de la correspondance, et de garder la portabilité, je vous suggère fortement d'utiliser la bitmasking méthode, que, contrairement à ce lié billet de blog, il est pas mauvaises pratiques.
Ce que c'est sur le champ de bits approche qui vous fait grincer des dents?
Les deux techniques ont leur place, et la seule décision que j'ai, c'est celui à utiliser:
Pour un simple "one-off" peu tripoter, j'utilise les opérateurs au niveau du bit directement.
Pour quelque chose de plus complexe - par exemple matériel de registre des cartes, le champ de bits approche gagne haut la main.
(au détriment de /un peu/plus d'
le niveau de verbosité à écrire.
plus robuste (quelle est la taille de "int",
de toute façon)
aussi vite que les opérateurs sur les bits.
un mélange d'un seul ou de plusieurs bits
les champs, et l'extraction de la
plusieurs bits de terrain implique des charges de
manuel de quarts de travail.
effectivement l'auto-documentation. Par
la définition de la structure et donc
nommer les éléments, je sais ce que c'est
censé faire.
Avec les opérateurs au niveau du bit, typique (bad) est la pratique d'un grand nombre de #définit pour les masques de bits.
Le seul inconvénient avec bitfields est de s'assurer que le compilateur a vraiment emballé l'objet dans la taille que vous vouliez. Je ne me souviens pas si c'est définir par la norme, donc un assert(sizeof(myStruct) == N) est utile pour vérifier.
De toute façon, bitfields ont été utilisés dans les logiciels GNU, pendant des décennies, et il n'a pas fait aucun mal. Je les aime comme paramètres de fonctions.
Je dirais que bitfields sont classique par opposition à des structures. Tout le monde sait comment ET les valeurs de régler diverses options off et le compilateur bout de très efficace des opérations bit à bit sur le CPU.
Fournir vous utilisez les masques et les tests dans le bon sens, l'abstraction, le compilateur devrait le rendre robuste, simple, lisible et propre.
Quand j'ai besoin d'un ensemble de commutateurs on/off, je vais continuer à les utiliser dans C.
La post de blog vous référer aux mentions
raw
de l'union de champ en tant que variante de la méthode d'accès pour bitfields.Les fins d'après le blog de l'auteur utilisé
raw
sont ok, mais si vous prévoyez de l'utiliser pour autre chose (par exemple, la sérialisation des champs de bits, réglage/vérification des bits individuels), la catastrophe est juste en attente pour vous, dans le coin. L'ordre des bits dans la mémoire est dépendants de l'architecture et de la mémoire rembourrage règles varient d'un compilateur de compilateur (voir wikipédia), de sorte que la position exacte de chaque champ de bits peut diffère, en d'autres termes, vous ne pouvez être sûr de bits deraw
chaque champ de bits correspond à.Toutefois, si vous ne prévoyez pas de le mélanger à mieux vous prendre
raw
et vous serez en sécurité.Eh bien, vous ne pouvez pas vous tromper avec la structure de la cartographie depuis deux champs sont utilisables ils peuvent être interchangeables.
Un avantage pour les champs de bits, c'est que vous pouvez facilement rassembler les options:
Dans certains environnements, tels que la lutte des options de protocole, il peut devenir très vieux d'avoir à titre individuel, de définir des options ou de l'utilisation de plusieurs paramètres ferry états intermédiaires de l'effet d'un résultat final.
Mais parfois, réglage du drapeau.blah et avoir la liste popup dans votre IDE est grande, surtout si vous êtes comme moi et ne me souviens pas le nom de l'indicateur que vous souhaitez définir sans cesse le référencement de la liste.
Personnellement, je vais parfois timide loin de déclarer les types boolean, car à un certain point, je vais avec la fausse impression que le champ j'ai juste basculé n'était pas dépendante (Pensez multi-thread de simultanéité) sur la r/w statut des autres "apparemment" les domaines indépendants qui partagent le même mot de 32 bits.
Mon vote, c'est qu'il dépend du contexte de la situation et, dans certains cas, les deux approches peuvent fonctionner sur une grande.
En C++, il suffit d'utiliser
std::bitset<N>
.Elle est sujette à erreur, oui. J'ai vu beaucoup d'erreurs dans ce genre de code, principalement parce que certaines personnes se sentent qu'ils devraient jouer avec ça et la logique métier dans un cadre totalement désorganisé de manière à créer l'entretien des cauchemars. Ils pensent que "réel", les programmeurs peuvent écrire
value |= mask;
,value &= ~mask;
ou pire encore des choses à tout place, et c'est ok. Encore mieux s'il existe un opérateur d'incrémentation autour d'un couple dememcpy
's, pointeur jette et quelle que soit obscur et le risque d'erreur de syntaxe se produit pour leur venir en tête sur le moment. Bien sûr, il n'y a pas besoin d'être cohérent et vous pouvez inverser les bits de deux ou trois façons différentes, distribués de façon aléatoire.Mon conseil serait:
SetBit(...)
etClearBit(...)
. (Si vous n'avez pas de classes en C, dans un module.) Pendant que vous y êtes, vous pouvez documenter toutes leurs comportements.Votre première méthode est préférable, à mon humble avis. Pourquoi occulter la question? Peu tripoter est vraiment une chose fondamentale. C le fait correctement. Endianess n'a pas d'importance. La seule chose que l'union de la solution n'est de nommer les choses. 11 pourrait être mystérieux, mais #défini par un nom significatif ou enum ed devrait suffire.
Programmeurs qui ne peut pas gérer les fondamentaux comme "|&^~" sont probablement dans la mauvaise ligne de travail.
Quand je google pour "les opérateurs de c"
Les trois premières pages sont:
http://en.wikipedia.org/wiki/Operators_in_C_and_C%2B%2B
http://h30097.www3.hp.com/docs/base_doc/DOCUMENTATION/V40F_HTML/AQTLTBTE/DOCU_059.HTM
http://www.cs.mun.ca/~michael/c/op.html
..donc je pense que l'argument de nouvelles personnes à la langue est un peu idiot.
J'ai presque toujours utiliser les opérations logiques avec un masque de bits, soit directement, soit sous forme d'une macro. par exemple,
d'ailleurs votre union définition dans la question d'origine ne fonctionnerait pas sur mon processeur/compilateur combinaison. Le type int est que de 16 bits de large et le champ de bits définitions sont 32. Afin de le rendre plus portable alors que vous auriez à définir un nouveau 32 bits de type que vous pouvez ensuite la carte à la base de type sur chaque cible de l'architecture dans le cadre du portage de l'exercice. Dans mon cas,
et dans l'exemple original
La superposition de l'int doit également être faite non signé.
Eh bien, je suppose que c'est une façon de le faire, mais je préfère toujours keep it simple.
Une fois que vous êtes habitué à elle, à l'aide de masques est simple, sans ambiguïté et portable.
Bitfields sont simples, mais elles ne sont pas portables sans avoir à effectuer un travail supplémentaire.
Si jamais vous avez à écrire MISRAcode compatible, MISRA lignes directrices froncement de sourcils sur bitfields, les syndicats, et beaucoup, beaucoup d'autres aspects de la C, afin d'éviter indéfini ou de mise en œuvre de comportement dépendant.
En général, celui qui est plus facile à lire et à comprendre est celui qui est également plus facile à maintenir. Si vous avez des collègues qui sont nouveaux à C, la plus "sécuritaire" approche sera probablement le plus facile pour eux de comprendre.
Bitfields sont très bien, sauf que les opérations de manipulation des bits ne sont pas atomiques, et peut donc conduire à des problèmes dans l'application multi-thread.
Par exemple, on pourrait supposer qu'une macro:
Définit une opération atomique, puisque |= est une déclaration. Mais l'ordinaire, le code généré par un compilateur de ne pas essayer de faire |= atomique.
Donc si plusieurs threads d'exécuter différentes opérations sur les bits un des bits est peut-être faux. Depuis deux threads de s'exécuter:
Le résultat peut être field' = champ OU mask1 OU mask2 (adresse), ou le résultat peut être field' = champ OU mask1 (pas d'adresse) ou le résultat peut être field' = champ OU mask2 (pas prévu).