Lunité usuelle de compte en informatique est loctet, car les ordinateurs manipulent les données par paquets de huit bits en général, et souvent de seize ou plus. Cependant, dans certains cas, on doit accéder à certains bits individuellement dans une donnée.
Pour cela, on dispose dopérateurs sur les types entiers, comme le décalage à gauche ou à droite (<<
, >>
), le « et » , le « ou » et le « ou exclusif » logiques (&
, |
, ^
).
Cependant, lorsquon doit faire de nombreux accès aux bits séparés dune donnée, cela devient trop long, et désagréable, de spécifier une opération logique chaque fois. Les structures à champs de bits permettent de résoudre ce problème.
Une telle structure est semblable à toute autre, mais derrière le nom des champs du type int
ou unsigned
, on précise un nombre de 1 à 16 indiquant la taille en bits du champ. Voici un exemple simple :
struct champbits { unsigned basbas : 4; unsigned bashaut : 4; unsigned hautbas : 4; int hauthaut : 4; };
Cette structure occupe seize bits (4 fois 4) en mémoire, soit la taille dun entier usuel. Notons quon aurait pu la déclarer plus brièvement ainsi :
struct champbits { unsigned basbas : 4, bashaut : 4, hautbas : 4; int hauthaut : 4; };
Lorsquun champ de bits est unsigned
, sa valeur varie de 0 à 2b -1, où b est le nombre de bits. Par exemple, le champ basbas
a une valeur de 0 à 15. Lorsque le champ est signed
, sa valeur varie de -2b-1à 2b-1-1, le bit de poids fort servant de bit de signe ; ainsi le champ hauthaut
varie de -8 à 7.
Les champs de bits sont utilisés comme des entiers ordinaires ; lors dune affectation, les bits excédentaires sont supprimés, les bits manquants sont nuls. Par exemple, si lon écrit :
champbits cb;int i = 30 // 30 == 0x1Ecb.bashaut = i; // met 0xE == 14 dans cb.bashauti = cb.bashaut; // maintenant i == 14
le résultat est de tronquer i
en ne conservant que ses quatre bits de poids faibles.
Si vous écrivez :
int i = -7890;champbits cb;cb = *(champbits*)&i; // recopiei
danscb
vous obtiendrez dans cb
la décomposition de -7890 en quatre parties, soit { 14, 2, 1, -2 }, indiquant que ce nombre vaut 0xE12E
en mémoire (dans sa forme sans signe).
Voici un autre exemple, un peu plus intéressant à notre avis. La fonction suivante calcule la valeur de la mantisse, de lexposant et du signe dun nombre à virgule flottante float
. Ces quantités sont réparties ainsi dans les quatre octets occupés par un float
(des bits de poids faibles aux forts) : 23 bits de mantisse, 8 bits dexposant (biaisé par 127), et un bit de signe :
void disseque(float f, int& signe, int& exposant long& mantisse){ struct dissq { unsigned mantisse1 : 16; unsigned mantisse2 : 7; unsigned exposant : 8; int signe : 1; } fb; fb = *(dissq*)&f; // recopie f dans fb exposant = fb.exposant -127; signe = fb.signe; mantisse = 0x800000 | fb.mantisse1 | (long(fb.mantisse2) << 16);}
Lexposant est biaisé, ce qui explique quil faille retirer 127. Quant à la mantisse, elle est ici en deux parties car on ne peut avoir de champs de bits de plus de seize bits. En outre, le bit le plus élevé (le vingt-quatrième), qui est toujours à 1, nest pas stocké dans le nombre, il faut le rajouter explicitement (doù le 0x800000
). La valeur du nombre est alors :
Nous avons vu précédemment que tous les bits des champs de bits étaient placés les uns derrière les autres, du moins significatif (déclaré en premier) au plus significatif. Il arrive que lon ne souhaite pas utiliser certains bits. Dans ce cas, il suffit de ne pas nommer le champ correspondant. Par exemple, les microprocesseurs de la famille 8086 ont un mot détat de seize bits, dont seuls quelques-uns ont un sens. Ainsi, le bit ZF est à 1 si la dernière opération a produit un résultat nul, à 0 sinon. Voici une structure reproduisant cette configuration :
struct flags { unsigned CF : 1; // retenue unsigned : 1; unsigned PF : 1; // parité unsigned : 1; unsigned AF : 1; // retenue auxiliaire unsigned : 1; unsigned ZF : 1; // zéro unsigned SF : 1; // signe unsigned TF : 1; // trap unsigned IF : 1; // autorisation dinterruption unsigned DF : 1; // direction unsigned OF : 1; // débordement unsigned : 4; }
Les bits non utilisés, au nombre de sept, figurent sans nom dans cette structure.
Signalons aussi que, lorsque lon demande au compilateur Turbo C++ daligner les données sur les mots de mémoire, il ne doit pas y avoir de champ de bits chevauchant une limite de mot, sinon il est décalé sur le mot suivant.
On notera que les champs de bits nont pas dadresse mémoire (il est illégal dutiliser lopérateur dadressage &
avec eux), puisquils ne se trouvent pas nécessairement sur une limite doctet. En outre le langage ne permet pas de les organiser en tableaux.
Les champs de bits peuvent procurer des facilités dans certains cas ; ils sont surtout utiles dans des applications très techniques faisant intervenir le matériel ou les périphériques.
Une structure peut avoir à la fois des champs de bits et des champs normaux, ainsi que des méthodes. Une classe et une union (ci-après) peuvent aussi en avoir.
Précédent | Sommaire | Suivant |