Techniques de masquage en C/C++ Introduction Le masquage de bits est une technique qui va permettre d’identifier dans une valeur binaire les bits sur lesquels une opération bit à bit est appliquée. Un masque peut ainsi être assimilé à un “pochoir” qui permet de peindre une zone précise sans risque de débordement. Selon le point de vue, ce “pochoir” peut aussi être considéré comme un moyen de protéger une zone de la peinture appliquée. Les masques de bits seront ainsi utilisés à chaque fois que l’on désire intervenir sur une seule portion d’une valeur binaire. Les domaines d’utilisation des masques de bit sont nombreux : le forçage à 1 ou à 0 d’un ou plusieurs bits le test de la valeur d’un ou plusieurs bits l’inversion de la valeurs d’un ou plusieurs bits la détermination du nombre de bits à 0 ou à 1 d’une valeur binaire l’inversion de l’ordre des bits d’une valeur binaire … Nous allons décrire ci-après les utilisations les plus courantes. Il s’avère que le masquage de bits est souvent mal maitrisé par les étudiants. Cette technique n’est pourtant pas si compliquée que cela si on se donne la peine de la pratiquer un petit peu, De plus, il faut savoir qu’elle est très souvent utilisée en informatique : programmation bas niveau → accès aux registres d’un microprosseur, accès aux broches d’entrée/sortie du système réseau → masques de sous-réseau imagerie → retouche photo, traitement d’images jeux vidéo → sprites, bit blit Forçage de bits à 0 ou à 1 Forçage d’un seul bit Forçage à 1 On utilise le OU binaire et le décalage à gauche : valeur = valeur | (1 << rang_bit) avec rang_bit la position du bit à forcer à 1 dans l’octet (0=le bit le plus à droite, 7=le bit le plus à gauche) La couleur rouge ainsi que la taille des caractères dans cette formule ainsi que dans celles à venir devraient vous faire prendre conscience qu’elles constituent quelque chose de très important à savoir par cœur de chez par cœur . Quant aux formules en bleu, elle constituent un “plus”. Exemple : // Forçage à 1 du bit de rang 6 d'un octet (le 7ième bit en partant de la droite) unsigned char octet = 0xA5; // 0b10100101 octet = octet | (1 << 6); // 'octet' contient 0b11100101 suite aux 2 étapes du calcul : // 1/ (1 << 6) -> 0b00000001 << 6 -> 0b01000000 // 2/ 10100101 // | 01000000 // ---------- // 11100101 // +-------> seul le 7ième bit a été impacté par le forçage à 1 Forçage à 0 On utilise le ET binaire, le décalage à gauche et le NON binaire: valeur = valeur & ~(1 << rang_bit) avec rang_bit la position du bit à forcer à 0 dans l’octet (0=le bit le plus à droite, 7=le bit le plus à gauche) Exemple : // Forçage à 0 du bit de rang 5 d'un octet (le 6ième bit) unsigned char octet = 0xA5; // 0b10100101 octet = octet & ~(1 << 5); // 'octet' contient 0b11100101 suite aux 3 étapes du calcul : // 1/ (1 << 5) -> 0b00000001 << 6 -> 0b00100000 // 2/ ~(1 << 5) -> 0b11011111 // 3/ 10100101 // & 11011111 // ---------- // 10000101 // +-------> seul le 6ième bit a été impacté par le forçage à 0 Forçage de plusieurs bits consécutifs Cette opération est utilisée moins fréquemment que la précédente mais peut toutefois s’avérer utile parfois. On utilise la même technique que précédemment mais avec un masque plus complexe. Comme mentionné dans le titre, la technique présentée ci-dessous fonctionne UNIQUEMENT si les bits à forcer sont consécutifs. Forçage à 1 On utilise le OU binaire, le décalage à gauche et la soustraction arithmétique : valeur = valeur | (((1 << nb_bit) - 1) << rang_bit) avec : nb_bit le nombre de bits à forcer rang_bit la position (0…n) dans l’octet, en partant de la droite, à partir de laquelle on désire réaliser le forçage. Forçage à 0 On utilise le ET binaire, le DÉCALAGE À GAUCHE, le NON binaire : valeur = valeur & ~(((1 << nb_bit)-1) << rang_bit) Inversion de la valeur de bits On utilise la même technique que le forçage à 1 mais en utilisant cette fois-ci le OU EXCLUSIF binaire. Inversion d’un seul bit On utilise le OU EXCLUSIF binaire et le décalage à gauche. valeur = valeur ^ (1 << numbit) Inversion de plusieurs bits On utilise le OU EXCLUSIF binaire, le décalage à gauche et la soustraction arithmétique. valeur = valeur ^ (((1 << nb_bit) - 1) << rang_bit) Rappel Si on souhaite inverser tous les bits d’un valeur binaire, il suffit d’utiliser : valeur = ~valeur 🖮 Exercice n° 1 Synthèse sur le forçage de bits Donner 3 exemples pour chacune des “formules” présentées dans cette partie du sujet : Forçage d’un seul bit à 1 et à 0 Forçage à 1 et à 0 de plusieurs bits Inversion de 1 et plusieurs bits (donc 6*3=18 exemples) Test de la valeur d’un bit Il suffit d’isoler la valeur du bit dans la condition d’une instruction de test du langage. En langage C/C++ : if(condition) … else … do … while(condition); while(condition) … for( …; condition; …) (condition) ? … : …; En effet, il faut savoir qu’une condition est considérée comme fausse si elle vaut 0 et vrai dans les autres cas (i.e différente de 0). Exemple : if( (valeur & (1 << rang_bit)) != 0 ) { // bit de rang 'rang_bit' est à 1 } else { // bit de rang 'rang_bit' est à 0 } 🖮 Exercice n° 2 Test de bit On se propose de simuler un chenillard (c’est-à-dire un mouvement lumineux qui se produit en allumant et éteignant successivement une série de lampes) à l’aide d’un programme. Le chenillard sera modélisé par un octet dans lequel un bit à 1 représentera une lampe allumée. Cet octet sera affiché à l’écran de façon à ce qu’un bit à 0 soit représenté par un point (.) et un bit à 1 par une étoile (*). Ex. : . . . * . . . . Coder une fonction void affiche(unsigned char octet) qui affiche les 8 caractères qui codent les 8 bits du paramètre octet ( . = 0, * = 1) suivis d’un retour à la ligne. L’affichage d’un seul caractère à l’écran peut être obtenu avec putc('A', stdout); après inclusion de l’entête <stdio.h>. Tester la fonction affiche() avec plusieurs valeurs de paramètre. Coder une fonction void chenillard() qui simule l’allumage “tournant” vers la gauche d’une seule lampe. Ex. : .......* ......*. .....*.. ....*... ...*.... ..*..... .*...... *....... .......* ......*. etc Une pause entre les différents affichages peut être obtenue à l’aide de la fonction sleep(delai_en_secondes) sous Linux après inclusion de l’entête <unistd.h>. Tester votre fonction chenillard() Faire évoluer la fonction chenillard() de manière à ce qu’elle prenne en paramètre un nombre de lampes voisines à allumer simultanément. Un exemple de ce qui est attendu est illustré ci-dessous. Your browser does not support the video tag. Opérations sur les champs de bits Dans ce qui précède, les opérations portaient sur un ou plusieurs bits qui représentaient chacun une information booléenne (1 information = 1 bit). Cependant, on rencontre des cas où on doit lire/écrire plusieurs informations codées sur plus d'1 bit dans une seule variable. Ex. : stocker dans un seul octet les couleurs de premier plan (foreground) et d’arrière plan (background) d’un texte. L’identifiant d’un hôte dans le réseau dans une adresse IPv4 pourrait constituer un autre exemple. Dans ces situations, les techniques précédentes (forçage, inversion, test d'1 bit) ne sont plus adaptées car l’ensemble des bits qui représente l’information doit être considéré comme un tout et non comme une séquence de bits ayant chacun une signification. Les opérations de lecture/écriture sur ce type d’information vont alors devoir se faire en plusieurs étapes. Lecture d’une information de plusieurs bits 3 étapes sont nécessaires : Lire l’intégralité de la variable dans laquelle l’information à lire est stockée Mettre à 0 les bits ne faisant pas partie de l’information à lire Faire glisser totalement à droite l’ensemble des bits de l’information. Exemple : reprenons le cas de l’octet de couleurs évoqué plus haut : les 4 bits de poids fort représentent une couleur de 1er plan les 4 bits de poids faible codent une couleur d’arrière plan. Admettons que la valeur de cet octet code l’information “magenta sur fond jaune” et que la palette des couleurs disponibles soit codée ainsi : 0 → Noir, 1 → Rouge, 2 → Vert, 3 → Jaune, 4 → Bleu, 5 → Magenta, 6 → Cyan, 7 → Blanc On aura donc : color = 0x53; La lecture de la couleur de 1er plan se codera en langage C/C++ de la manière suivante : unsigned char color = 0x53; // variable qui doit contenirt la couleur du 1er plan unsigned char foreground; // Étape n°1 : Lecture de l'intégralité des couleurs (1ier plan et arrière plan) foreground = color; // Étape n°2 : Forçage à 0 des 4 bits associés au codage de l'arrière plan foreground &= 0xF0; // Étape n°3 : Décalage à droite de la couleur de 1ier plan foreground >>= 4; // => 'foreground' contient désormais la valeur 5 qui code effectivement la couleur magenta Le code précédent peut également s’écrire en une seule ligne : foreground = (color & 0xF0) >> 4; Il faut veiller à ce que les variables utilisées soit non signées. En effet, si une information utilise le bits de poids fort (MSB : Most Significant Bit) de la variable englobante , le décalage à droite d’une telle valeur avec un MSB à 1 depuis une variable signée (char, int, long) provoquera un bourrage à gauche avec des 1. La valeur finale sera ainsi faussée. Exemple : admettons que les 2 bits de poids fort d’une variable code les 4 valeurs possibles que peut prendre un style de texte : 0 → normal, 1 → gras, 2 → souligné, 3 → italique. Admettons que cette variable ait la valeur 0b10xxxxxx (texte gras). La lecture du style dans une variable signée donnera, suite au décalage de 6 bits vers la droite, la valeur 0b11111110 c’est-à-dire 0xFE soit -2 en décimal et non +2 comme attendu. Dans l’exemple précédent, l’étape n°2 n’est pas obligatoire puisque le codage de la couleur de 1ier plan occupe l’ensemble des bits de poids fort de l’octet. Cependant, dans un premier temps, je vous conseille de toujours réaliser les 3 étapes. Écriture d’une information sur plusieurs bits 3 étapes sont encore nécessaires : Décaler la valeur de l’information à écrire de façon à ce que ses bits soient correctement positionnés vis à vis de la variable englobante. Mettre à 0 l’ensemble des bits de l’information initiale dans la variable englobante Écrire la nouvelle information dans la variable englobante avec un simple OU bit à bit de façon à ne pas impacter les bits environnants. La mise à 0 de l’information initiale de l’étape n°2 est indispensable car le OU binaire final peut forcer à 1 les bits d’origine quelque soient leurs valeurs (0 ou 1). Par contre, il ne peut pas les forcer à 0 s’ils étaient auparavant à 1. Exemple : Modifions la couleur de texte de l’exemple précédent pour qu’elle devienne “cyan sur fond jaune” (bon, d’accord, cette combinaison de couleurs ne permet pas de discerner correctement le texte mais c’est pour l’exemple ) unsigned char color = 0x53; // couleur initiale : magenta sur fond jaune unsigned char foreground = 6; // code de la nouvelle couleur de 1ier plan : cyan // Étape n°1 : Décalage de la couleur de 1ier plan au niveau des 4 bits de poids fort // de la variable englobante foreground <<= 4; // Étape n°2 : Forçage à 0 des 4 bits de poids fort associés à la couleur de // 1ier plan dans la variable englobante. color &= 0x0F; // ou color &= ~(((1 << 4)-1) << 4) // Étape n°3 : stockage de l'information color |= foreground; // => 'color' contient la valeur 0x63 qui code effectivement cyan sur fond jaune. 🞄 🞄 🞄 Opérateurs bit à bit en C/C++ Design patterns