Skip to main content

C Bitwise Operations

Bitwise operations are fundamental operations that manipulate individual bits in a value. These operations are extremely efficient and commonly used in embedded systems, device drivers, cryptography, and other areas where direct manipulation of bits is necessary.

Introduction to Bits and Binary

Before diving into bitwise operations, let's quickly review what bits are:

  • A bit is the smallest unit of data in computing, representing either 0 or 1
  • 8 bits form a byte, which can represent values from 0 to 255
  • In C, integers are typically represented using multiple bytes (4 bytes for an int on most systems)

Bitwise Operators in C

C provides six bitwise operators that allow you to manipulate individual bits:

OperatorNameDescription
&ANDSets each bit to 1 if both corresponding bits are 1
|ORSets each bit to 1 if at least one corresponding bit is 1
^XORSets each bit to 1 if exactly one corresponding bit is 1
~NOTInverts all the bits (0 becomes 1, 1 becomes 0)
<<Left ShiftShifts all bits to the left by a specified number of positions
>>Right ShiftShifts all bits to the right by a specified number of positions

Bitwise AND (&)

The bitwise AND operator compares each bit of two operands and returns 1 if both bits are 1, otherwise 0.

c
#include <stdio.h>

int main() {
unsigned char a = 12; // 00001100 in binary
unsigned char b = 25; // 00011001 in binary
unsigned char result = a & b; // 00001000 in binary (8 in decimal)

printf("a & b = %d\n", result);
return 0;
}

Common Uses:

  • Masking (extracting specific bits)
  • Checking if a bit is set (non-zero)
  • Clearing specific bits (setting them to 0)

Bitwise OR (|)

The bitwise OR operator compares each bit of two operands and returns 1 if at least one bit is 1.

c
#include <stdio.h>

int main() {
unsigned char a = 12; // 00001100 in binary
unsigned char b = 25; // 00011001 in binary
unsigned char result = a | b; // 00011101 in binary (29 in decimal)

printf("a | b = %d\n", result);
return 0;
}

Common Uses:

  • Setting specific bits to 1
  • Combining bit flags

Bitwise XOR (^)

The bitwise XOR (exclusive OR) operator compares each bit of two operands and returns 1 if exactly one bit is 1 (not both).

c
#include <stdio.h>

int main() {
unsigned char a = 12; // 00001100 in binary
unsigned char b = 25; // 00011001 in binary
unsigned char result = a ^ b; // 00010101 in binary (21 in decimal)

printf("a ^ b = %d\n", result);
return 0;
}

Common Uses:

  • Toggle bits (changing 0 to 1 and 1 to 0)
  • Simple encryption (XORing with a key)
  • Finding differences between bit patterns

Bitwise NOT (~)

The bitwise NOT operator inverts all bits of an operand (0 becomes 1, 1 becomes 0).

c
#include <stdio.h>

int main() {
unsigned char a = 12; // 00001100 in binary
unsigned char result = ~a; // 11110011 in binary (243 in decimal for an 8-bit unsigned char)

printf("~a = %d\n", result);
return 0;
}
caution

The result of the NOT operation depends on the size of the data type. For example, if a is a 32-bit int with value 12, ~a would be -13 due to two's complement representation.

Left Shift (<<)

The left shift operator shifts all bits to the left by a specified number of positions. New bits on the right are filled with 0s.

c
#include <stdio.h>

int main() {
unsigned char a = 12; // 00001100 in binary
unsigned char result = a << 2; // 00110000 in binary (48 in decimal)

printf("a << 2 = %d\n", result);
return 0;
}

Common Uses:

  • Multiplication by powers of 2 (a << n equals a * 2^n)
  • Creating masks with specific bit patterns
  • Packing multiple values into a single variable

Right Shift (>>)

The right shift operator shifts all bits to the right by a specified number of positions.

c
#include <stdio.h>

int main() {
unsigned char a = 12; // 00001100 in binary
unsigned char result = a >> 2; // 00000011 in binary (3 in decimal)

printf("a >> 2 = %d\n", result);
return 0;
}
note

For signed integers, the behavior of right shift depends on the implementation. Most C compilers perform an arithmetic right shift, which preserves the sign bit.

Common Uses:

  • Division by powers of 2 (a >> n equals a / 2^n for unsigned integers)
  • Extracting specific bit fields

Common Bit Manipulation Techniques

1. Setting a Bit

c
unsigned int setBit(unsigned int num, int position) {
return num | (1 << position);
}

2. Clearing a Bit

c
unsigned int clearBit(unsigned int num, int position) {
return num & ~(1 << position);
}

3. Toggling a Bit

c
unsigned int toggleBit(unsigned int num, int position) {
return num ^ (1 << position);
}

4. Checking if a Bit is Set

c
int isBitSet(unsigned int num, int position) {
return (num & (1 << position)) != 0;
}

5. Extracting Bits

c
unsigned int extractBits(unsigned int num, int startPos, int length) {
return (num >> startPos) & ((1 << length) - 1);
}

Practical Examples

Example 1: Packing RGB Values

c
#include <stdio.h>

// Pack RGB values (0-255 each) into a single 32-bit integer
unsigned int packRGB(unsigned char r, unsigned char g, unsigned char b) {
return ((r << 16) | (g << 8) | b);
}

// Extract RGB components from packed value
void unpackRGB(unsigned int packed, unsigned char *r, unsigned char *g, unsigned char *b) {
*r = (packed >> 16) & 0xFF;
*g = (packed >> 8) & 0xFF;
*b = packed & 0xFF;
}

int main() {
unsigned char r = 255, g = 128, b = 64;
unsigned int packed = packRGB(r, g, b);

printf("Packed RGB: 0x%08X\n", packed);

unsigned char r2, g2, b2;
unpackRGB(packed, &r2, &g2, &b2);
printf("Unpacked: R=%d, G=%d, B=%d\n", r2, g2, b2);

return 0;
}

Example 2: Bit Flags

c
#include <stdio.h>

// Define bit flags for file permissions (similar to Unix)
#define READ_PERMISSION (1 << 0) // 001 in binary
#define WRITE_PERMISSION (1 << 1) // 010 in binary
#define EXEC_PERMISSION (1 << 2) // 100 in binary

void printPermissions(unsigned char permissions) {
printf("Permissions: %c%c%c\n",
(permissions & READ_PERMISSION) ? 'r' : '-',
(permissions & WRITE_PERMISSION) ? 'w' : '-',
(permissions & EXEC_PERMISSION) ? 'x' : '-');
}

int main() {
unsigned char permissions = 0;

// Grant read permission
permissions |= READ_PERMISSION;
printPermissions(permissions);

// Grant write and execute permissions
permissions |= (WRITE_PERMISSION | EXEC_PERMISSION);
printPermissions(permissions);

// Revoke write permission
permissions &= ~WRITE_PERMISSION;
printPermissions(permissions);

return 0;
}

Performance Considerations

Bitwise operations are extremely efficient because they map directly to processor instructions. They're much faster than arithmetic operations for certain tasks:

  • Checking if a number is even: (num & 1) == 0 is faster than num % 2 == 0
  • Multiplying/dividing by powers of 2: num << 3 is faster than num * 8

Summary

Bitwise operations are powerful tools in C programming that allow for efficient manipulation of individual bits. They're essential for:

  • Low-level hardware programming
  • Memory-efficient data structures
  • Performance optimization
  • Setting and checking flags
  • Cryptographic algorithms
  • Network protocols

While they may seem complex at first, mastering bitwise operations gives you fine-grained control over your data and can lead to more efficient code in many situations.

Practice Exercises

  1. Write a function to count the number of set bits (1s) in an integer
  2. Write a function to determine if an integer is a power of 2
  3. Implement a bit vector (an array-like structure where each element is a single bit)
  4. Write a function to swap two variables without using a temporary variable (using XOR)


If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)