Opérations au niveau des bits en C - Bitwise operations in C

Dans le langage de programmation C , les opérations peuvent être effectuées au niveau du bit à l' aide d' opérateurs au niveau du bit .

Les opérations au niveau du bit sont contrastées par les opérations au niveau de l' octet qui caractérisent les contreparties logiques des opérateurs au niveau du bit, les opérateurs ET, OU, NON. Au lieu de s'exécuter sur des bits individuels, les opérateurs de niveau octet s'exécutent sur des chaînes de huit bits (appelées octets) à la fois. La raison en est qu'un octet est normalement la plus petite unité de mémoire adressable (c'est-à-dire des données avec une adresse mémoire unique ).

Cela s'applique également aux opérateurs au niveau du bit, ce qui signifie que même s'ils n'opèrent que sur un bit à la fois, ils ne peuvent accepter rien de plus petit qu'un octet comme entrée.

Tous ces opérateurs sont également disponibles en C ++ , et beaucoup de C-famille des langues.

Opérateurs au niveau du bit

C fournit six opérateurs pour la manipulation de bits .

symbole Opérateur
& ET au niveau du bit
| OU inclusif au niveau du bit
^ XOR au niveau du bit (OU exclusif)
<< décalage à gauche
>> décalage à droite
~ PAS au niveau du bit (complément à un) (unaire)

ET au niveau du bit &

un peu peu b a & b (a ET b)
0 0 0
0 1 0
1 0 0
1 1 1

L'opérateur ET au niveau du bit est une seule esperluette : &. C'est juste une représentation de AND qui fait son travail sur les bits des opérandes plutôt que sur la valeur de vérité des opérandes. L'ET binaire au niveau du bit effectue une conjonction logique (indiquée dans le tableau ci-dessus) des bits à chaque position d'un nombre sous sa forme binaire.

Par exemple, en travaillant avec un octet (le type char) :

     11001000  
   & 10111000 
     -------- 
   = 10001000

Le bit le plus significatif du premier nombre est 1 et celui du deuxième nombre est également 1, donc le bit le plus significatif du résultat est 1 ; dans le deuxième bit le plus significatif, le bit du deuxième nombre est zéro, nous avons donc le résultat 0.

OU au niveau du bit |

un peu peu b un | b (a OU b)
0 0 0
0 1 1
1 0 1
1 1 1

Semblable à ET au niveau du bit, le OU au niveau du bit effectue une disjonction logique au niveau du bit. Son résultat est un 1 si l'un des bits est à 1 et zéro uniquement lorsque les deux bits sont à 0. Son symbole est |ce qu'on peut appeler un tube.

      11001000  
    | 10111000 
      -------- 
    = 11111000

XOR au niveau du bit ^

un peu peu b a ^ b (a XOR b)
0 0 0
0 1 1
1 0 1
1 1 0

Le XOR au niveau du bit (exclusif ou) effectue une disjonction exclusive , ce qui équivaut à ajouter deux bits et à rejeter le report. Le résultat n'est nul que lorsque nous avons deux zéros ou deux uns. XOR peut être utilisé pour basculer les bits entre 1 et 0. Ainsi, i = i ^ 1lorsqu'il est utilisé dans une boucle, ses valeurs basculent entre 1 et 0.

      11001000  
    ^ 10111000 
      -------- 
    = 01110000

Opérateurs de quart

Il existe deux opérateurs de décalage au niveau du bit. Elles sont

  • Décalage à droite ( >>)
  • Décalage à gauche ( <<)

Décalage à droite >>

Le symbole de l'opérateur de décalage à droite est >>. Pour son fonctionnement, il nécessite deux opérandes . Il décale chaque bit de son opérande gauche vers la droite. Le nombre suivant l'opérateur décide du nombre de places où les bits sont décalés (c'est-à-dire l'opérande de droite). Ainsi en faisant ch >> 3tous les bits seront décalés vers la droite de trois places et ainsi de suite.

Cependant, notez qu'une valeur d'opérande de décalage qui est soit un nombre négatif, soit supérieure ou égale au nombre total de bits dans cette valeur entraîne un comportement indéfini . Par exemple, lors du décalage d'un entier non signé de 32 bits, une quantité de décalage de 32 ou plus serait indéfinie.

Exemple:

Si la variable chcontient le modèle binaire 11100101, alors ch >> 1produira le résultat 01110010, et ch >> 2produira 00111001.

Ici, des espaces blancs sont générés simultanément à gauche lorsque les bits sont décalés vers la droite. Lorsqu'elle est effectuée sur un type non signé ou une valeur non négative dans un type signé, l'opération effectuée est un décalage logique , provoquant le remplissage des blancs par 0s (zéros). Lorsqu'il est effectué sur une valeur négative dans un type signé, le résultat est techniquement défini par l'implémentation (dépendant du compilateur), mais la plupart des compilateurs effectueront un décalage arithmétique , ce qui remplira le blanc avec le bit de signe défini de l'opérande gauche.

Le décalage vers la droite peut être utilisé pour diviser un motif de bits par 2, comme indiqué :

i = 14; // Bit pattern 00001110
j = i >> 1; // here we have the bit pattern shifted by 1 thus we get 00000111 = 7 which is 14/2

Utilisation de l'opérateur à droite

L'utilisation typique d'un opérateur de décalage à droite en C peut être vue à partir du code suivant.

Exemple:

#include <stdio.h>
    
void showbits( unsigned int x )
{
    int i=0;
    for (i = (sizeof(int) * 8) - 1; i >= 0; i--)
    {
       putchar(x & (1u << i) ? '1' : '0');
    }
    printf("\n");
}

int main( void )
{
    int j = 5225;
    printf("%d in binary \t\t ", j);
    /* assume we have a function that prints a binary string when given 
       a decimal integer 
    */
    showbits(j);

    /* the loop for right shift operation */
    for (int m = 0; m <= 5; m++)
    {
        int n = j >> m;
        printf("%d right shift %d gives ", j, m);
        showbits(n);
    }
    return 0;
}

La sortie du programme ci-dessus sera

5225 in binary           00000000000000000001010001101001
5225 right shift 0 gives 00000000000000000001010001101001
5225 right shift 1 gives 00000000000000000000101000110100
5225 right shift 2 gives 00000000000000000000010100011010
5225 right shift 3 gives 00000000000000000000001010001101
5225 right shift 4 gives 00000000000000000000000101000110
5225 right shift 5 gives 00000000000000000000000010100011

Décalage à gauche <<

Le symbole de l'opérateur de décalage à gauche est <<. Il décale chaque bit de son opérande de gauche vers la gauche du nombre de positions indiqué par l'opérande de droite. Il fonctionne à l'opposé de celui de l'opérateur à droite. Ainsi, en faisant ch << 1dans l'exemple ci-dessus, nous avons 11001010. Les espaces vides générés sont remplis par des zéros comme ci-dessus.

Cependant, notez qu'une valeur d'opérande de décalage qui est soit un nombre négatif, soit supérieure ou égale au nombre total de bits dans cette valeur entraîne un comportement indéfini . Ceci est défini dans la norme ISO 9899:2011 6.5.7 Opérateurs de décalage au niveau du bit . Par exemple, lors du décalage d'un entier non signé de 32 bits, une quantité de décalage de 32 ou plus serait indéfinie.

Le décalage à gauche peut être utilisé pour multiplier un nombre entier par des puissances de 2 comme dans

int i = 7;    // Decimal 7 is Binary (2^2) + (2^1) + (2^0) = 0000 0111
int j = 3;    // Decimal 3 is Binary         (2^1) + (2^0) = 0000 0011
k = (i << j); // Left shift operation multiplies the value by 2 to the power of j in decimal
              // Equivalent to adding j zeros to the binary representation of i
              // 56 = 7 * 2^3
              // 0011 1000 = 0000 0111 << 0000 0011

Exemple : un programme d'addition simple

Le programme suivant ajoute deux opérandes en utilisant AND, XOR et shift gauche (<<).

#include <stdio.h>

int main( void )
{
    unsigned int x = 3, y = 1, sum, carry;
    sum = x ^ y; // x XOR y
    carry = x & y; // x AND y
    while (carry != 0)
    {
        carry = carry << 1; // left shift the carry
        x = sum; // initialize x as sum
        y = carry; // initialize y as carry
        sum = x ^ y; // sum is calculated
        carry = x & y; /* carry is calculated, the loop condition is 
                          evaluated and the process is repeated until 
                          carry is equal to 0.
                        */
    }
    printf("%u\n", sum); // the program will print 4
    return 0;
}

Opérateurs d'affectation au niveau du bit

C fournit un opérateur d'affectation composé pour chaque opération arithmétique binaire et au niveau du bit (c'est-à-dire chaque opération qui accepte deux opérandes). Chacun des opérateurs d'affectation au niveau du bit composé effectue l'opération binaire appropriée et stocke le résultat dans l'opérande de gauche.

Les opérateurs d'affectation au niveau du bit sont les suivants :

symbole Opérateur
&= affectation ET au niveau du bit
|= affectation OU inclusive au niveau du bit
^= affectation OU exclusif au niveau du bit
<<= affectation à gauche
>>= affectation à droite

Équivalents logiques

Quatre des opérateurs au niveau du bit ont des opérateurs logiques équivalents. Ils sont équivalents en ce qu'ils ont les mêmes tables de vérité. Cependant, les opérateurs logiques traitent chaque opérande comme n'ayant qu'une seule valeur, vraie ou fausse, plutôt que de traiter chaque bit d'un opérande comme une valeur indépendante. Les opérateurs logiques considèrent que zéro est faux et toute valeur différente de zéro est vraie. Une autre différence est que les opérateurs logiques effectuent une évaluation de court-circuit .

Le tableau ci-dessous correspond aux opérateurs équivalents et montre a et b comme opérandes des opérateurs.

Au niveau du bit Logique
a & b a && b
a | b a || b
a ^ b a != b
~a !a

!=a la même table de vérité que ^mais contrairement aux vrais opérateurs logiques, en soi !=n'est pas à proprement parler un opérateur logique. En effet, un opérateur logique doit traiter de la même manière toute valeur différente de zéro. Pour être utilisé comme un opérateur logique, il !=faut d'abord normaliser les opérandes. Une logique non appliquée aux deux opérandes ne changera pas la table de vérité qui en résulte mais garantira que toutes les valeurs non nulles sont converties en la même valeur avant la comparaison. Cela fonctionne car !un zéro donne toujours un un et !toute valeur différente de zéro donne toujours un zéro.

Exemple:

/* Equivalent bitwise and logical operator tests */
#include <stdio.h>

void testOperator(char* name, unsigned char was, unsigned char expected);

int main( void )
{
   // -- Bitwise operators -- //

   //Truth tables packed in bits
   const unsigned char operand1    = 0x0A; //0000 1010
   const unsigned char operand2    = 0x0C; //0000 1100
   const unsigned char expectedAnd = 0x08; //0000 1000
   const unsigned char expectedOr  = 0x0E; //0000 1110
   const unsigned char expectedXor = 0x06; //0000 0110
	
   const unsigned char operand3    = 0x01; //0000 0001
   const unsigned char expectedNot = 0xFE; //1111 1110

   testOperator("Bitwise AND", operand1 & operand2, expectedAnd);
   testOperator("Bitwise  OR", operand1 | operand2, expectedOr);
   testOperator("Bitwise XOR", operand1 ^ operand2, expectedXor);
   testOperator("Bitwise NOT", ~operand3, expectedNot);	
   printf("\n");

   // -- Logical operators -- //

   const unsigned char F = 0x00; //Zero
   const unsigned char T = 0x01; //Any nonzero value

   // Truth tables packed in arrays

   const unsigned char operandArray1[4]    = {T, F, T, F};
   const unsigned char operandArray2[4]    = {T, T, F, F};
   const unsigned char expectedArrayAnd[4] = {T, F, F, F};
   const unsigned char expectedArrayOr[4]  = {T, T, T, F};
   const unsigned char expectedArrayXor[4] = {F, T, T, F};
	
   const unsigned char operandArray3[2]    = {F, T};
   const unsigned char expectedArrayNot[2] = {T, F};

   int i;
   for (i = 0; i < 4; i++)
   {
      testOperator("Logical AND", operandArray1[i] && operandArray2[i], expectedArrayAnd[i]);
   }
   printf("\n");

   for (i = 0; i < 4; i++)
   {
      testOperator("Logical  OR", operandArray1[i] || operandArray2[i], expectedArrayOr[i]);
   }
   printf("\n");

   for (i = 0; i < 4; i++)
   {
      //Needs ! on operand's in case nonzero values are different
      testOperator("Logical XOR", !operandArray1[i] != !operandArray2[i], expectedArrayXor[i]);
   }
   printf("\n");

   for (i = 0; i < 2; i++)
   {
      testOperator("Logical NOT", !operandArray3[i], expectedArrayNot[i]);
   }
   printf("\n");

   return 0;
}

void testOperator( char* name, unsigned char was, unsigned char expected )
{
   char* result = (was == expected) ? "passed" : "failed";
   printf("%s %s test, was: %X expected: %X \n", name, result, was, expected);    
}

La sortie du programme ci-dessus sera

 Bitwise AND passed, was: 8 expected: 8
 Bitwise  OR passed, was: E expected: E
 Bitwise XOR passed, was: 6 expected: 6
 Bitwise NOT passed, was: FE expected: FE
 
 Logical AND passed, was: 1 expected: 1
 Logical AND passed, was: 0 expected: 0
 Logical AND passed, was: 0 expected: 0
 Logical AND passed, was: 0 expected: 0
 
 Logical  OR passed, was: 1 expected: 1
 Logical  OR passed, was: 1 expected: 1
 Logical  OR passed, was: 1 expected: 1
 Logical  OR passed, was: 0 expected: 0
 
 Logical XOR passed, was: 0 expected: 0
 Logical XOR passed, was: 1 expected: 1
 Logical XOR passed, was: 1 expected: 1
 Logical XOR passed, was: 0 expected: 0
 
 Logical NOT passed, was: 1 expected: 1
 Logical NOT passed, was: 0 expected: 0

Voir également

Les références

Liens externes