Published on

Comment bien utiliser le "bit masking"

 8 mins
Authors
  • avatar
    Name
    Léo Delpon
    Twitter

Prérequis:

  • Connaître le principe de opérateurs logiques
  • Avoir une bonne connaissance du principe d'un drapeau
  • Avoir de bonnes connaissances en C

Contexte

Dans le monde de la programmation, il y a une astuce super cool appelée "bit masking". Imaginez que vous avez un tiroir et que vous voulez y ranger différents objets sans mélanger le tout. Le bit masking, c'est un peu comme organiser ce tiroir de manière hyper efficace. Au lieu de prendre plein d'espace avec chaque objet, vous trouvez un moyen de les ranger tous ensemble dans un petit espace, tout en sachant exactement où chaque chose se trouve.

C'est pareil avec les bits, ces minuscules interrupteurs qui ne connaissent que deux états : 0 et 1. Le bit masking joue avec ces interrupteurs pour lire, écrire, allumer ou éteindre certains d'entre eux précisément dans une variable. C'est comme si vous aviez une télécommande qui contrôle chaque objet de votre tiroir séparément. Vous pouvez dire "hé, lampe, allume-toi!" ou "radio, tais-toi!" sans affecter les autres objets.

Pourquoi c'est génial ? Parce que ça permet de stocker un tas d'infos (comme des oui/non ou des états différents) dans un tout petit espace. C'est super économique en termes de mémoire, et ça donne un contrôle de ninja sur ce qui se passe dans votre code. C'est un peu comme jouer à Tetris avec les données, en les emboîtant de manière super précise pour ne pas perdre d'espace.

Quel est l'intérêt du "bit masking" ?

Efficacité de la mémoire : Permet de stocker de multiples flags ou états dans une seule variable entière.

Manipulation précise : Offre la capacité de manipuler individuellement les bits d'une variable pour contrôler différents aspects ou configurations d'un programme.

Performance : Les opérations bit à bit sont généralement très rapides, car elles sont directement supportées par le matériel.

Exemple concret

Imaginons que nous souhaitons gérer des états de permissions pde façon optimisé, comment faire ?

Pour ce faire, on définit quelques flags de permissions pour la lecture, l'écriture, l'execution par exemple.

#include <stdio.h>

#define FLAG_READ 0x00
#define FLAG_WRITE 0x01
#define FLAG_EXECUTE 0x02

Après avoir défini nos états, on va pouvoir assigner certains flags à notre variable de permission.


int main() {
  unsigned char permission = 0;

  permission |= FLAG_READ | FLAG_EXECUTE;
}

Dans notre permission, nous avons donc FLAG_READ et FLAG_EXECUTE. Pour ceux qui ne comprenne pas très bien ce principe, je vais vous le démystifier :

FLAG_READ | FLAG_EXECUTE => FLAG_RESULT
0001 | 0100 => 0101

permission |= FLAG_RESULT
0000 | 0101 => 0101

Pourquoi j'utilise unsigned char plutôt que unsigned int8_t ?

char est garanti d'exister, d'avoir une largeur d'au moins 8 bits et de pouvoir représenter soit tous les entiers compris entre -127 et 127 inclus (s'il est signé) ou entre 0 et 255 (s'il n'est pas signé).

int8_t n'est pas garanti d'exister (et oui, il existe des plates-formes sur lesquelles ce n'est pas le cas), mais s'il existe, il est garanti comme un type entier signé à complément à deux de 8 bits sans bits de remplissage ; il est donc capable de représenter tous les entiers compris entre -128 et 127, et rien d'autre.

Après avoir pris en compte ces permissions, il est maintenant important de voir comment les vérifier.

Vérifier que nos flags sont bien présent dans la variable de permission

Pour ce faire, nous allons utiliser l'opérateur logique & qui va permettre la vérification. Le résultat sera égal à 0100, ce qui correspond à un resultat non nul.


if(permission & FLAG_RESULT)
  execute_module();

Comment retirer une permission ?

C'est très simple, on va utiliser un autre opérateur qui est l'opérateur NOT.


permision ~= FLAG_READ;

if(!(permission & FLAG_READ))
  log("Permission has been revoked\n");

En faisant cela, on vient de retirer la permission de lecture dans la variable et de vérifier que la permission a bien été enlevée !