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 feedback@compilenrun.com. I’d greatly appreciate your feedback! :)