Légende des schémas |
N |
Index d'un bit du champs de bits de la variable originale, marque la fin d'un octet (en notation hexadécimale) |
N |
Index d'un bit du champs de bits de la variable originale, marque un position intermédiaire d'un octet (en notation hexadécimale) |
N |
Index d'un bit du champs de bits de la variable originale, marque le début d'un octet (en notation hexadécimale) |
N |
Contenu binaire du bit de la variable originale à l'index (en notation binaire: 0 ou 1) |
N |
Contenu binaire du bit du masque à l'index (en notation binaire: 0 ou 1) |
N |
Contenu binaire du bit résultant de l'opération du masque sur la variable originale à l'index (en notation binaire: 0 ou 1) |
Pourquoi cet objet |
L'intérêt de cet objet n'est pas de se substituer à une variable, bien que cela soit possible. Non, il réside dans la propriété même d'une variable : stocker des valeurs et notamment de stocker plusieurs valeurs à l'intérieur d'une seule afin d'optimiser la mémoire. Le cas le plus emblématique est la variable BOOL. C'est une variable de type int, c'est à dire qu'elle occupe 4 octets (32 bits) en mémoire alors qu'elle n'est destinée à accueillir que deux valeurs : le 0 (FALSE) et le 1 (TRUE). Donc elle utilise qu'un bit, les autres (31) ne le sont pas. On peut dire que ces 31 bits sont gaspillés. C'est bien sûr un cas extrême, mais bien souvent l'on a besoin uniquement de stocker une grande quantité d'états de type Non/Oui et il faut se résoudre à utiliser une variable pour chacun d'eux dont uniquement un bit sera réellement exploité. La solution réside dans l'exploitation, non pas d'une variable comme entité propre, mais dans chacun des bits qui la compose. Cet objet est à utiliser uniquement avec des variables décimales (char, short, int, long, longlong) non flottantes (float, double). |
Décomposition d'une variable |
L'élément primaire est le bit, qui ne peut accepter que deux états, le 0 et le 1. On fait tout avec cela. Pour pouvoir stocker des informations supérieures à 1, on a assemblé ces bits par paquets de 8; c'est l'octet. Les bits sont positionnés l'un à la suite de l'autre en commençant par la droite. Le schéma suivant montre la représentation d'un octet :
Afin de pouvoir stocker un nombre dans ces cases, il faut le décomposer en puissances de 2 (1, 2, 4, 8, 16, 32, 64, etc...), par exemple 140 se décompose en : (1 * 2^7) + (0 * 2^6) + (0 * 2^5)+ (0 * 2^4) + (1 * 2^3) + (1 * 2^2) + (0 * 2^1) + (0 * 2^0) = 128 + 8 + 4 = 140, soit :
Si l'on pose tous les bits d'un octet, on obtient le nombre 255. Donc un octet (composé de 8 bits) peut contenir un nombre pouvant s'étaler de 0 à 255. Si l'on a besoin de stocker un nombre supérieur, il suffit de "coller" deux octets, on a ainsi 16 bits qui peuvent contenir un nombre pouvant s'étaler de 0 à 65535. On peut "coller" deux, quatre ou huit 8 octets (oubliez la possibilité de "coller" 3, 5 ou 6 octets). C'est cette possibilité de manipuler chacun des bits d'une variable qui nous est utile pour l'objet EValue. A titre indicatif, une variable de type char est composé d'un octet (soit 8 bits), le type short de deux octets (16 bits), le int et le long de quatre octets (32 bits) et le longlong de huit octets (64 bits). |
Instancier un objet EValue |
L'objet EValue est un template, il ne s'instancie pas exactement comme une classe. Vous devez spécifier en plus un type de portée de l'objet : Objet père + <Type de portée > + nom de l'objet instancié(argument d'initialisation du constructeur "optionnel");
Cet objet, nommé eValue, est une instanciation d'un objet EValue, considère le type de l'objet comme un uchar, et initialise la variable membre m_tValue avec la valeur 140. L'objet ne peut travailler que sur une étendue d'un octet (8 bits) car le type uchar a une densité d'un octet. Si vous essayez d'accéder à des bits au delà de cette limite, il n'y aura aucun effet. Si l'on désire travailler sur 8 octets (64 bits), on doit instancier l'objet avec le type longlong (ou ulonglong).
|
Manipuler les bits d'une variable |
Pour manipuler un bit d'une variable on a besoin d'une variable sur laquelle travailler, d'un masque de bits servant de calque et d'un opérateur de bits. La variable contient les bits qui doivent être manipulés, le masque de bits contient les coordonnées du/des bit(s) à manipuler et l'opérateur de bits indique l'opération à effectuer. Ajouter un bit Il s'agit de mettre la valeur 1 dans l'un des bits de la variable. Le masque de bits doit contenir le(s) bit(s) à poser et l'opérateur de bits est le caractère | (or, transposé dans l'objet EValue par les fonctions EValue::MaskAdd...(...)).
Que c'est-il passé ? Tout d'abord il faut savoir que le masque de bits vient se calquer sur la variable, le bit à la position 0x00 du masque ne travaillera que sur le bit à la position 0x00 de la variable, le bit à la position 0x01 du masque ne travaillera que sur le bit 0x01 de la variable, etc... Les opérations se font en parallèle. Si le bit 0x00 du masque contient la valeur 1, le bit correspondant dans la variable est mis à 1. Si le bit 0x00 du masque contient la valeur 0 alors le bit correspondant dans la variable n'est pas modifié, etc... Dans notre exemple, seul le bit 0x05 du masque est posé, donc l'opérateur de bits | va mettre la valeur 1 au bit 0x05 de la variable. Les autres bits de la variable ne sont pas touchés. Supprimer un bit Il s'agit de mettre la valeur 0 dans l'un des bits de la variable. Le masque de bits doit contenir le(s) bit(s) à supprimer et l'opérateur de bits est le caractère & (and, transposé dans l'objet EValue par les fonctions EValue::MaskSuppress...(...)).
Que c'est-il passé ? Si le bit 0x00 du masque contient la valeur 1, le bit correspondant dans la variable est mis à 0. Si le bit 0x00 du masque contient la valeur 0 alors le bit correspondant dans la variable n'est pas modifié, etc... Dans notre exemple, seuls les bits 0x02 et 0x03 du masque sont posés, donc l'opérateur de bits & va mettre la valeur 0 au bit 0x02 et bit 0x03 de la variable. Les autres bits de la variable ne sont pas touchés. Tester un bit Il s'agit de tester si un ou des bits sont positionnés à 1 dans une variable. Là aussi on utilise l'opérateur &.
|
Tirer partie de l'objet EValue |
A partir d'un exemple simple, nous allons voir comment utiliser l'objet EValue au mieux en quatre étapes. Nous avons un programme et nous désirons pouvoir stocker 5 informations : - faut-il afficher les astuces au démarrage (oui/non) ? Etape 1 On peut écrire le code suivant :
La déclaration de ces variables occupe 20 octets, soit 160 bits. Comme on peut le constater seulement 7 bits sont utilisés réellement (soit 4%), donc 153 bits sont gaspillés (soit ±19 octets). Etape 2 On peut optimiser cela par le code suivant :
Là, la déclaration de ces variables occupe 5 octets (soit 40 bits). Comme on peut le constater seulement 7 bits sont utilisés réellement (soit 17%), donc 33 bits sont gaspillés (soit ±4 octets). Etape 3 C'est un progrès mais l'on peut faire mieux en utilisant l'objet EValue :
La déclaration de ces variables occupe 2 octets (soit 16 bits). Comme on peut le constater 7 bits sont utilisés réellement (soit 43%), donc 9 bits sont gaspillés (soit ±1 octet). Il y a encore une technique d'optimisation possible. Etape 4 Jusqu'à présent nous nous sommes focalisé sur la possibilité d'exploiter l'objet pour stocker des valeurs de types OUI/NON en n'utilisant qu'un bit. Mais il est possible d'utiliser les bits par paquets. La variable stockant la langue à utiliser peut être aussi stockée dans un objet EValue ? Sachant que ses valeurs possibles s'étendent de 0 (en) à 4 (sp 111 en binaire), on peut caser ces trois octets dans les bits 0x04, 0x05 et 0x6 par la méthode suivante :
Pour récupérer la langue utilisée, il suffit de tester les bits 0x04, 0x05 et 0x06 en utilisant le masque 0x70 (01110000 en binaire) défini à l'énumération LanguageAll de la manière suivante : Langue à utiliser = objet EValue & LanguageAll; Pour fixer la langue à utiliser, il suffit de supprimer tous les bits concernant les langues puis d'ajouter ceux de la langue à utiliser. Il est important de supprimer d'abord tous les bits concernant les langues sinon il pourrait y avoir une superposition des bits de l'ancienne langue et de la nouvelle (la fonction EValue::MaskSuppressAdd( T tMaskToSuppress, T tMaskToAdd ); a été spécialement écrite pour cela) : MaskSuppressAdd( LanguageAll, LanguageFrench );
La déclaration de la variable occupe 1 octet (soit 8 bits). Comme on peut le constater 7 bits sont utilisés réellement (soit 87%), donc 1 bit est gaspillé. L'économie par rapport à la première déclaration est de 19 octets sur 20 octets (soit 95%) et de 152 bits sur 160 bits. Note Certes l'utilisation poussée de l'objet EValue (étape 4) demande une rédaction plus rigoureuse (que l'étape 1) mais elle apporte beaucoup d'avantages : 1. Economie énorme de la mémoire. Imaginez cent mille structures initialisées par l'étape 1, cela demanderait près de 2 Mo de mémoire alors qu'avec l'étape 4 cela ne réclamerait que 100 Ko de mémoire. 2. Le processeur doit traiter ces données, plus il y de données et plus le système sera ralenti. 3. La sauvegarde de 2 Mo de données est plus lourde que la sauvegarde de 100 Ko que ce soit en terme de temps ou de place sur le disque dur (Non ?). 4. L'utilisation des opérateurs de bits par l'objet EValue ne réclame que très peu de temps au processeur car ces opérateurs font parties intégrante du langage machine, donc très rapides. |