Introduction

All information stored in a computer’s memory is in the form of a series of bits. For example, the integer 10 (in decimal base) stored as a 16-bit two’s complement number will be encoded by the following bit sequence:

  0000 0000 0000 1010

When we talk about the position of a bit in a binary number, index 0 corresponds to the rightmost bit (also called Least Significant Bit, or LSB in short), index 1 to the bit to the left of the lowest bit, and so on. The leftmost bit of the binary number is called the Most Significant Bit (MSB).

In C language, you can write a number in binary (base 2) by prefixing it with 0b. For example, if we want to represent the number 26 in binary in eight positions (i.e. 8-bit representation), in C language this gives 0b00011010. In hexadecimal (base 16), this is equivalent to 0x1A. Recall that, in C language, hexadecimal numbers are prefixed with 0x.

On the other hand, note that on some machines or operating systems, an int could use 2, 4 or 8 bytes (so 16, 32 or 64 bits). Therefore, if we want to declare variables with a determined number of bits, the C language introduces a new type class:

- int8_t (8-bit signed integer)
- uint8_t (8-bit unsigned integer)
- uint16_t (16-bit unsigned integer)
- etc.

These types are defined in the stdint.h header and guarantee that the variables declared thus have the desired number of bits regardless of the machine or operating system used.

Bitwise operators

“Bitwise operators” in C language allow you to set, modify or test one or more bits of data. These operators are:

  • NOT “~“
  • AND “&”
  • OR “|“
  • XOR (eXclusive OR) “^”
  • SHL (SHift Left) “<<“
  • SHR (SHift Right) “>>“

NOT operator “~”

The unary operator NOT flips the state of a bit according to the following table:

A NOT A
0 1
1 0

In C language, the tilda character ~ is used to represent the NOT operator. It acts on each bit of the value. Example :

   uint16_t a = 1;  /* <=> a = 0b0000000000000001; */
   uint16_t b = ~a; /* <=> b = 0b1111111111111110; */

AND operator “&”

The binary operator AND combines the state of two bits according to the following table:

A B A AND B
0 0 0
0 1 0
1 0 0
1 1 1

In C language, the symbol & represents the AND operator and acts on each bit of the operands. Example:

   uint16_t a = 0xF0F0; /* <=> a = 0b1111000011110000; */
   uint16_t b = 0x00FF; /* <=> b = 0b0000000011111111; */
   uint16_t c = a & b;  /* <=> c = 0b0000000011110000; that's 0x00F0 in hexadecimal */

OR operator “|”

The binary operator OR combines the state of two bits according to the following table:

A B A OR B
0 0 0
0 1 1
1 0 1
1 1 1

The symbol | represents the OR operator in C language. It acts on each bit of the operands. Example:

   uint16_t a = 0xF0F0; /* <=> a = 0b1111000011110000; */
   uint16_t b = 0x00FF; /* <=> b = 0b0000000011111111; */
   uint16_t c = a | b;  /* <=> c = 0b1111000011111111; that's 0xF0FF in hexadecimal */

XOR (eXclusive OR) operator “^”

The binary operator XOR combines the state of two bits according to the following table:

A B A XOR B
0 0 0
0 1 1
1 0 1
1 1 0

The character ^ represents the XOR operator in C language. It acts on each bit of the operands. Example:


   uint16_t a = 0xF0F0; /* <=> a = 0b1111000011110000; */
   uint16_t b = 0x00FF; /* <=> b = 0b0000000011111111; */
   uint16_t c = a ^ b;  /* <=> c = 0b1111000000001111; that's 0xF00F in hexadecimal */

SHR (SHift Right) operator “>>”

result = op_1 >> op_2

This operator allows to shift a given operand op_1 by a certain number of bits to the right (the amount of shift is specified by op_2). The low-order bits of the operand op_1 are lost, and the high-order bits are replaced by zeros.

In C language, a combination of two right-facing angle brackets >> implements the SHR operator. Example:

   uint16_t a = 0xF0F0; /* <=> a = 0b1111000011110000; */
   uint16_t b = 2;      /* <=> b = 0b0000000000000010; */
   uint16_t c = a >> b; /* <=> c = 0b0011110000111100; that's 0x3C3C in hexadecimal */

SHL (SHift Left) operator “<<”

result = op_1 << op_2

This operator allows to shift a given operand op_1 by a certain number of bits to the left (the amount of shift is specified by op_2). The most significant bits of the operand op_1 are lost, and the least significant bits are replaced by zeros.

In C language, a combination of two left-facing angle brackets << represents the SHL operator. Example:

   uint16_t a = 0xF0F0; /* <=> a = 0b1111000011110000; */
   uint16_t b = 2;      /* <=> b = 0b0000000000000010; */
   uint16_t c = a << b; /* <=> c = 0b1100001111000000; that's 0xC3C0 in hexadecimal */

Using Bitwise Operations

Set a bit in a value

The idea is to combine the value with a mask using the OR operator. Indeed, as indicated by the truth table of the OR operator, the bits of the mask which are at 0 will leave the corresponding bits in the initial value unchanged and the bits of the mask which are at 1 will prevail. Example :

   /* set bit 4 of a: */
   uint16_t a = 0x000F; /* <=> a = 0b0000000000001111; */
   uint16_t b = 0x0010; /* <=> b = 0b0000000000010000; b is our mask! */
   uint16_t c = a | b;  /* <=> c = 0b0000000000011111; that's  0x001F in hexadecimal */

   printf ("%04X OU %04X = %04X\n, a, b, c);

To build the mask, simply apply a left shift to the (unsigned) constant 1. The amount of shifting corresponds to the position of the bit we want to set. Example:

   uint16_t b = 1u << 0;  /* <=> b = 0b0000000000000001;  <=> bit  0 is set */
   uint16_t b = 1u << 2;  /* <=> b = 0b0000000000000100;  <=> bit  2 is set */
   uint16_t b = 1u << 15; /* <=> b = 0b1000000000000000;  <=> bit 15 is set */

NOTE: The letter ‘u’ suffixed to the number ‘1’ above indicates that this is an ‘unsigned constant’.

Clear a bit in a value

Similar to the previous case, we need to apply a ‘mask’ to our value. This time, however, we need to devise the mask around the AND operator. Indeed, as indicated by the truth table of the AND operator, the bits of the mask which are at 1 will leave the corresponding bits in the initial value unchanged and the bits of the mask which are at 0 will prevail. Example :

   /* Clear bit 3 of a : */
   uint16_t a = 0x000F; /* <=> a = 0b0000000000001111; */
   uint16_t b = 0xFFF7; /* <=> b = 0b1111111111110111; b is our mask! */
   uint16_t c = a & b;  /* <=> c = 0b0000000000000111; that's 0x0007 in hexadecimal */

   printf ("%04X ET %04X = %04X\n, a, b, c);

To build the mask, simply apply a left shift to the (unsigned) constant 1 and then flip the result with the NOT operator. The amount of shifting corresponds to the position of the bit we want to clear. Example:

   uint16_t b = ~(1u << 0);  /* <=> b = 0b1111111111111110 <=> bit  0 is cleared */
   uint16_t b = ~(1u << 2);  /* <=> b = 0b1111111111111011 <=> bit  2 is cleared */
   uint16_t b = ~(1u << 15); /* <=> b = 0b0111111111111111 <=> bit 15 is cleared */

Read the state of a bit in a value

We again need to use a mask here and the AND operator. The mask should contain 0 except for the bit to be tested which is set to 1. Thus, the result will contain 0s for the 0 bits of the mask and the state of the evaluated bit (at bit=1 of the mask). That is, if the final result is 0, the tested bit is equal to 0, otherwise it is equal to 1.

   /* test the state of bit 2 of a: */
   uint16_t a = 0x000F; /* <=> a = 0b0000000000001111; */

   if (a & (1u << 2))
      puts("bit 2 == 1");   
   else
      puts("bit 2 == 0");