Bitwise Operators in C

Bitwise operators, also called bit-by-bit operators, in C allow you to manipulate individual bits of integer numbers, offering low-level control often essential for optimizing operations such as flag management, masks, and bit shifts for calculation purposes.

With six available operators (two for shifts, four for boolean operations), the language offers considerable flexibility in handling information at the binary level.

Bitwise Operators

The C language provides six bitwise operators, which operate on integer data at the individual bit level.

We will first discuss the two shift or bitwise scrolling operators, followed by the other four bitwise operators (bitwise complement, bitwise AND, bitwise XOR, and bitwise OR).

Bitwise Shift Operators

The bitwise shift operators can transform the binary representation of an integer by moving its bits left or right. C provides two shift operators, shown in the following Table:

Symbol Meaning
<< left shift
>> right shift
Table 1: Bit-by-bit scrolling operators in C.

The operands for << and >> can be of any integer type (including char). Both operands are promoted to integers and the result has the type of the left operand after promotion.

  • The value of i << j is the result of shifting the bits in i leftward by j positions. For each bit that slides out of the left end of i, a 0 bit enters from the right.

    Left Shift Operation
    Picture 1: Left Shift Operation
  • The value of i >> j is the result of shifting the bits in i rightward by j positions. If it is an unsigned type or if the value of i is non-negative, zeros are added from the left as needed. If i is a negative number, the result depends on the implementation: in some compilers zeros will be added, in others the sign bit will be preserved.

    Right Shift Operation
    Picture 2: Right Shift Operation
Hint

Prefer unsigned for Shift Operators

It is preferable to perform shift operations only on unsigned numbers, in order to avoid implementation-dependent behaviors.

In the following example, the effect of applying shift operators to the number 13 (represented in binary) is shown. For simplicity, we assume using 16-bit short:

unsigned short i, j;

i = 13;    /* i now equals 13 (binary 0000000000001101) */
j = i << 2; /* j now equals 52 (binary 0000000000110100) */
i >> 2;    /* i now equals 3  (binary 0000000000000011) */

As these examples show, no operator modifies its operands. To modify the value of a variable by shifting its bits, we will use the combined assignment operators <<= and >>=:

i = 13;   /* i now equals 13 (binary 0000000000001101) */
i <<= 2;  /* i now equals 52 (binary 0000000000110100) */
i >>= 2;  /* i now equals 13 (binary 0000000000001101) */
Note

Precedence of Shift Operators

Bitwise shift operators have lower precedence than arithmetic operators, which can cause surprises. For example, i << 2 + 1 means (i << 2) + 1, not (i << (2 + 1)).

Bitwise Complement, AND, Exclusive OR, and Inclusive OR

The following Table lists the other bitwise operators.

Symbol Meaning
~ bitwise complement
& bitwise AND
^ XOR (exclusive or)
| OR (inclusive or)
Table 2: Other bitwise operators in C.
  • The ~ operator is unary; integer promotions are performed on it.
  • The other operators (&, ^, |) are binary and the usual arithmetic conversions apply to them.

The ~, &, ^, and | operators perform boolean operations on all bits of their respective operands:

  • ~ (complement) inverts the bits: 0s become 1s and 1s become 0s.
  • & performs the boolean AND operation on each pair of corresponding bits.
  • ^ and | are similar, they perform a boolean OR operation on the corresponding bits. However:
    • ^ (XOR) produces 0 when both bits have value 1, otherwise it produces 1.
    • | (OR) produces 1 if at least one of the corresponding bits equals 1.
Note

Bitwise Operators are not Logical Operators

Do not confuse the bitwise operators & and | with the logical operators && and ||.

Bitwise operators sometimes produce the same result as logical operators, but they are not equivalent.

In the following example, the effect of ~, &, ^, and | is illustrated:

unsigned short i, j, k;

i = 21;   /* i now equals 21 (binary 0000000000010101) */
j = 56;   /* j now equals 56 (binary 0000000000111000) */
k = ~i;   /* k now equals 65514 (binary 1111111111101010) */
k = i & j;  /* k now equals 16 (binary 0000000000010000) */
k = i ^ j;  /* k now equals 45 (binary 0000000000101101) */

(The value shown for ~i is based on the assumption that an unsigned short occupies 16 bits.)

The ~ operator deserves special attention, as it allows us to handle bits at a low level in a more portable way. If we need an integer in which all bits are equal to 1, we can write ~0, which does not depend on the number of bits in an integer. Similarly, if we need an integer whose bits are all 1 except the last one, we could write ~0x1f.

Precedence of Bitwise Operators

Each of the operators ~, &, ^, and | has different precedence. From top to bottom:

  1. ~
  2. &
  3. ^
  4. |

Consequently, we can combine these operators in expressions without having to use parentheses. For example, we could write i & ~j | k instead of (i & (~j)) | k and i ^ j & ~k instead of i ^ (j & (~k)). Of course, nothing prevents us from using parentheses to avoid confusion.

Note

Precedence of Bitwise and Relational Operators

The precedence of &, ^, and | is lower than that of relational and equality operators. Consequently, statements like the following do not have the desired effect:

if (status & 0x4000 != 0)

Instead of checking whether status & 0x4000 is different from zero, the statement evaluates 0x4000 != 0 (which has the value 1) and then checks whether status & 1 is not zero.

Combined Assignment Operators for Bitwise

The combined assignment operators &=, ^=, |= correspond respectively to the bitwise operators &, ^, and |:

i = 21;   /* i is now 21 (binary 0000000000010101) */
i = 56;   /* i is now 56 (binary 0000000000111000) */
i &= 5;   /* i is now 56 & 5 = 0b111000 & 0b00101 = 40 (decimal) */
i ^= j;   /* i is now 40 ^ j */
i |= j;   /* i is now ... etc. */

As seen, these assignment forms allow you to directly modify the contents of a variable by applying a bitwise operation to it.

In Summary

The key concepts studied in this lesson are:

  • Shift Operators (<< and >>): shift the bits of a value left or right, filling the spaces with zeros or preserving the sign.
  • Bitwise operators (~, &, ^, |): allow complements, AND, XOR, and OR between individual bits of two operands.
  • Precedence: shift and bitwise operators have different precedences from arithmetic, relational, and logical operators. It is important to use parentheses if you want a different evaluation order.
  • Safety and portability: to avoid undefined or implementation-dependent behaviors, it is advisable to use shifts on unsigned types and pay attention to the differences between bitwise (&, |) and logical (&&, ||) operators.
  • Combined assignments: <<=, >>=, &=, ^=, |= allow you to perform both the bitwise operation and the assignment, making the code more compact.