web-dev-qa-db-fra.com

Comment lire / écrire des bits arbitraires en C / C ++

En supposant que j'ai un octet b avec la valeur binaire de 11111111

Comment lire par exemple une valeur entière de 3 bits commençant au deuxième bit ou écrire une valeur entière de quatre bits commençant au cinquième bit?

30
dtech

Quelque 2 ans et plus après avoir posé cette question, j'aimerais l'expliquer de la façon dont je voudrais qu'elle soit expliquée à l'époque, alors que j'étais encore un newb complet et serait le plus bénéfique pour les personnes qui veulent comprendre le processus.

Tout d'abord, oubliez l'exemple de valeur "11111111", qui n'est pas vraiment tout à fait adapté à l'explication visuelle du processus. Alors laissez la valeur initiale être 10111011 (187 décimales) qui illustrera un peu mieux le processus.

1 - comment lire une valeur de 3 bits à partir du deuxième bit:

    ___  <- those 3 bits
10111011 

La valeur est 101 ou 5 en décimal, il y a 2 façons possibles de l'obtenir:

  • masque et décalage

Dans cette approche, les bits nécessaires sont d'abord masqués avec la valeur 00001110 (14 décimales) après quoi il est déplacé en place:

    ___
10111011 AND
00001110 =
00001010 >> 1 =
     ___
00000101

L'expression serait: (value & 14) >> 1

  • décalage et masque

Cette approche est similaire, mais l'ordre des opérations est inversé, ce qui signifie que la valeur d'origine est décalée puis masquée par 00000111 (7) pour ne laisser que les 3 derniers bits:

    ___
10111011 >> 1
     ___
01011101 AND
00000111
00000101

L'expression serait: (value >> 1) & 7

Les deux approches impliquent la même quantité de complexité et ne diffèrent donc pas en termes de performances.

2 - comment écrire une valeur de 3 bits à partir du deuxième bit:

Dans ce cas, la valeur initiale est connue, et lorsque c'est le cas dans le code, vous pourrez peut-être trouver un moyen de définir la valeur connue sur une autre valeur connue qui utilise moins d'opérations, mais en réalité, c'est rarement le Dans ce cas, la plupart du temps, le code ne connaîtra ni la valeur initiale, ni celle qui doit être écrite.

Cela signifie que pour que la nouvelle valeur soit "épissée" avec succès en octets, les bits cibles doivent être mis à zéro, après quoi la valeur décalée est "épissée" en place, ce qui est la première étape:

    ___ 
10111011 AND
11110001 (241) =
10110001 (masked original value)

La deuxième étape consiste à décaler la valeur que nous voulons écrire dans les 3 bits, disons que nous voulons changer cela de 101 (5) à 110 (6)

     ___
00000110 << 1 =
    ___
00001100 (shifted "splice" value)

La troisième et dernière étape consiste à épisser la valeur d'origine masquée avec la valeur "d'épissage" décalée:

10110001 OR
00001100 =
    ___
10111101

L'expression pour l'ensemble du processus serait: (value & 241) | (6 << 1)

Bonus - comment générer les masques de lecture et d'écriture:

Naturellement, l'utilisation d'un convertisseur binaire en décimal est loin d'être élégante, en particulier dans le cas des conteneurs 32 et 64 bits - les valeurs décimales deviennent folles. Il est possible de générer facilement les masques avec des expressions, que le compilateur peut résoudre efficacement lors de la compilation:

  • lire le masque pour "masquer et déplacer": ((1 << fieldLength) - 1) << (fieldIndex - 1), en supposant que l'index au premier bit est 1 (pas zéro)
  • lire le masque pour "shift and mask": (1 << fieldLength) - 1 (l'index ne joue pas de rôle ici car il est toujours décalé sur le premier bit
  • masque d'écriture: inversez simplement l'expression de masque "masquer et décaler" avec le ~ opérateur

Comment ça marche (avec le champ 3 bits commençant au deuxième bit des exemples ci-dessus)?

00000001 << 3
00001000  - 1
00000111 << 1
00001110  ~ (read mask)
11110001    (write mask)

Les mêmes exemples s'appliquent à des entiers plus larges et à une largeur de bits arbitraire et à la position des champs, les valeurs de décalage et de masque variant en conséquence.

Notez également que les exemples supposent un entier non signé, ce que vous voulez utiliser pour utiliser des entiers comme alternative de champ de bits portable (les champs de bits réguliers ne sont en aucun cas garantis par la norme comme portables), à la fois vers la gauche et vers la droite insérer un 0 de remplissage, ce qui n'est pas le cas avec le décalage vers la droite d'un entier signé.

Encore plus facile:

Utilisation de cet ensemble de macros (mais uniquement en C++ car il repose sur la génération de fonctions membres):

#define GETMASK(index, size) (((1 << (size)) - 1) << (index))
#define READFROM(data, index, size) (((data) & GETMASK((index), (size))) >> (index))
#define WRITETO(data, index, size, value) ((data) = ((data) & (~GETMASK((index), (size)))) | ((value) << (index)))
#define FIELD(data, name, index, size) \
  inline decltype(data) name() { return READFROM(data, index, size); } \
  inline void set_##name(decltype(data) value) { WRITETO(data, index, size, value); }

Vous pouvez opter pour quelque chose d'aussi simple que:

struct A {
  uint bitData;
  FIELD(bitData, one, 0, 1)
  FIELD(bitData, two, 1, 2)
};

Et implémentez les champs de bits en tant que propriétés auxquelles vous pouvez facilement accéder:

A a;
a.set_two(3);
cout << a.two();

Remplacez decltype par typeof de gcc pré-C++ 11.

103
dtech

Vous devez déplacer et masquer la valeur, donc par exemple ...

Si vous voulez lire les deux premiers bits, il vous suffit de les masquer comme ceci:

int value = input & 0x3;

Si vous voulez le compenser, vous devez décaler N bits vers la droite, puis masquer les bits que vous voulez:

int value = (intput >> 1) & 0x3;

Pour lire trois bits comme vous l'avez demandé dans votre question.

int value = (input >> 1) & 0x7;
13
Geoffrey

Vous devez effectuer une opération de décalage et de masque (ET). Soit b n'importe quel octet et p l'indice (> = 0) du bit dont vous voulez prendre n bits (> = 1).

Vous devez d'abord déplacer à droite b par p fois:

x = b >> p;

Ensuite, vous devez masquer le résultat avec n ceux:

mask = (1 << n) - 1;
y = x & mask;

Vous pouvez tout mettre dans une macro:

#define TAKE_N_BITS_FROM(b, p, n) ((b) >> (p)) & ((1 << (n)) - 1)
6
Claudix

utilisez simplement ceci et feelfree:

#define BitVal(data,y) ( (data>>y) & 1)      /** Return Data.Y value   **/
#define SetBit(data,y)    data |= (1 << y)    /** Set Data.Y   to 1    **/
#define ClearBit(data,y)  data &= ~(1 << y)   /** Clear Data.Y to 0    **/
#define TogleBit(data,y)     (data ^=BitVal(y))     /** Togle Data.Y  value  **/
#define Togle(data)   (data =~data )         /** Togle Data value     **/

par exemple:

uint8_t number = 0x05; //0b00000101
uint8_t bit_2 = BitVal(number,2); // bit_2 = 1
uint8_t bit_1 = BitVal(number,1); // bit_1 = 0

SetBit(number,1); // number =  0x07 => 0b00000111
ClearBit(number,2); // number =0x03 => 0b0000011
4
Hamid

"Comment puis-je par exemple lire une valeur entière de 3 bits à partir du deuxième bit?"

int number = // whatever;
uint8_t val; // uint8_t is the smallest data type capable of holding 3 bits
val = (number & (1 << 2 | 1 << 3 | 1 << 4)) >> 2;

(J'ai supposé que le "deuxième bit" est le bit # 2, c'est-à-dire le troisième bit vraiment.)

3
user529758

Pour lire les octets, utilisez std :: bitset

const int bits_in_byte = 8;

char myChar = 's';
cout << bitset<sizeof(myChar) * bits_in_byte>(myChar);

Pour écrire, vous devez utiliser des opérateurs bit à bit tels que & ^ | & << >>. assurez-vous d'apprendre ce qu'ils font.

Par exemple, pour avoir 00100100, vous devez définir le premier bit sur 1 et le décaler 5 fois avec les opérateurs << >>. si vous voulez continuer à écrire, continuez à régler le premier bit et à le décaler. cela ressemble beaucoup à une vieille machine à écrire: vous écrivez et déplacez le papier.

Pour 00100100: définissez le premier bit sur 1, décalez 5 fois, réglez le premier bit sur 1 et décalez 2 fois:

const int bits_in_byte = 8;

char myChar = 0;
myChar = myChar | (0x1 << 5 | 0x1 << 2);
cout << bitset<sizeof(myChar) * bits_in_byte>(myChar);
2
MasterMastic
int x = 0xFF;   //your number - 11111111

Comment puis-je par exemple lire une valeur entière de 3 bits à partir du deuxième bit

int y = x & ( 0x7 << 2 ) // 0x7 is 111
                         // and you shift it 2 to the left
1
Luchian Grigore

Si vous continuez à récupérer des bits de vos données, vous souhaiterez peut-être utiliser un champ de bits. Il vous suffira de configurer une structure et de la charger avec uniquement des uns et des zéros:

struct bitfield{
    unsigned int bit : 1
}
struct bitfield *bitstream;

puis plus tard, chargez-le comme ceci (en remplaçant char par int ou toutes les données que vous chargez):

long int i;
int j, k;
unsigned char c, d;

bitstream=malloc(sizeof(struct bitfield)*charstreamlength*sizeof(char));
for (i=0; i<charstreamlength; i++){
    c=charstream[i];
    for(j=0; j < sizeof(char)*8; j++){
        d=c;
        d=d>>(sizeof(char)*8-j-1);
        d=d<<(sizeof(char)*8-1);
        k=d;
        if(k==0){
            bitstream[sizeof(char)*8*i + j].bit=0;
        }else{
            bitstream[sizeof(char)*8*i + j].bit=1;
        }
    }
}

Accédez ensuite aux éléments:

bitstream[bitpointer].bit=...

ou

...=bitstream[bitpointer].bit

Tout cela suppose que vous travaillez sur i86/64, et non sur arm, car arm peut être gros ou peu endian.

0
Dominic Mason