Now that you're comfortable passing behavior through function pointers, let's zoom in on data at the bit level. Bitwise operations let you work with individual bits, which is useful when you want to save memory or control hardware directly. They also let you compress multiple states into a single integer.
New Terms in This Lesson
- Bit: The smallest unit that stores either 0 or 1
- Bit Mask: A value used to isolate or manipulate specific bits
- Flag: A bit you turn on/off to represent state
- Shift Operation: Moving bits left or right
Key Ideas
Study Notes
- Estimated time: About 60 minutes
- Prereqs: Integer sizes, logical operators, and basic familiarity with binary notation
- Goal: Understand each bitwise operator, design flags and masks, and use shifts for fast math
C provides six bitwise operators: &, |, ^, ~, <<, and >>. & is bitwise AND, | OR, ^ XOR, ~ NOT, << left shift, >> right shift. They work only on integer types and perform logic on each bit position. At the beginning, focus on unsigned values so you don't get tripped up by sign bits.
We'll walk through the following:
- Write out bit patterns using binary and confirm how each operator works
- Define flags and learn how to set, clear, and toggle them
- Pack sensor states or permissions into a single integer
- Use shifts for fast multiplication/division and simple data packing
Code Walkthrough
Basic Bitwise Operations
#include <stdio.h>
void print_bits(unsigned int value) {
int i;
for (i = 7; i >= 0; i--) {
unsigned int mask = 1u << i;
printf("%u", (value & mask) ? 1u : 0u);
}
printf("\n");
}
int main(void) {
unsigned int a = 0xAA; // 10101010
unsigned int b = 0xF0; // 11110000
printf("a & b = ");
print_bits(a & b);
printf("a | b = ");
print_bits(a | b);
printf("a ^ b = ");
print_bits(a ^ b);
printf("~a = ");
print_bits((unsigned char)(~a));
return 0;
}
This example uses 0xAA and 0xF0 so you can follow along even in C11/C17 environments. print_bits shows the upper eight bits so you can visualize results. Because ~ is applied after integer promotion, cast or mask the result to the width you want to inspect.
Defining and Manipulating Flags
#include <stdio.h>
enum {
FLAG_READ = 1 << 0, // 0001
FLAG_WRITE = 1 << 1, // 0010
FLAG_EXEC = 1 << 2, // 0100
FLAG_SYNC = 1 << 3 // 1000
};
void print_status(unsigned int flags) {
printf("perm: R=%d W=%d X=%d S=%d\n",
(flags & FLAG_READ) != 0,
(flags & FLAG_WRITE) != 0,
(flags & FLAG_EXEC) != 0,
(flags & FLAG_SYNC) != 0);
}
int main(void) {
unsigned int flags = 0;
flags |= FLAG_READ;
flags |= FLAG_WRITE;
print_status(flags);
flags &= ~FLAG_WRITE;
flags |= FLAG_SYNC;
print_status(flags);
flags ^= FLAG_EXEC;
print_status(flags);
return 0;
}
|= turns on a flag, &= ~ clears a specific flag, and ^= toggles it. You'll see these patterns in file permissions, event systems, and more.
Grouping Sensor States into Bits
#include <stdio.h>
#define SENSOR_TEMP (1u << 0)
#define SENSOR_HUMID (1u << 1)
#define SENSOR_LIGHT (1u << 2)
#define SENSOR_MOTION (1u << 3)
unsigned int read_sensors(int temperature, int humidity, int light, int motion) {
unsigned int status = 0;
if (temperature > 30) {
status |= SENSOR_TEMP;
}
if (humidity < 30) {
status |= SENSOR_HUMID;
}
if (light < 100) {
status |= SENSOR_LIGHT;
}
if (motion) {
status |= SENSOR_MOTION;
}
return status;
}
void handle_status(unsigned int status) {
if (status & SENSOR_TEMP) {
printf("temperature alert\n");
}
if (status & SENSOR_HUMID) {
printf("humidity alert\n");
}
if (status & SENSOR_LIGHT) {
printf("light alert\n");
}
if (status & SENSOR_MOTION) {
printf("motion detected\n");
}
}
int main(void) {
unsigned int status = read_sensors(32, 25, 90, 1);
handle_status(status);
return 0;
}
Packing several states into one integer keeps function parameters simple and lets you combine conditions effortlessly.
Fast Math with Shifts
#include <stdio.h>
int main(void) {
unsigned int value = 5;
printf("value << 1 = %u\n", value << 1); // 5 * 2
printf("value << 3 = %u\n", value << 3); // 5 * 8
unsigned int packed = (0x0A << 4) | 0x03; // upper 4 bits + lower 4 bits
printf("packed = 0x%X\n", packed);
unsigned int high = (packed >> 4) & 0x0F;
unsigned int low = packed & 0x0F;
printf("high=%u, low=%u\n", high, low);
return 0;
}
Shifts go beyond multiplication: you can slice data into pieces or glue them together. Embedded systems constantly tweak individual bits inside registers, so combining shifts and masks is a must. Be careful with right shifts on signed integers—implementations may handle sign bits differently—so start with unsigned values.
Why It Matters
- Bitwise operations shrink memory usage and let you pass state compactly.
- You can interpret every bit in a hardware register, file format, or network protocol.
- Shifts act as quick multiply/divide replacements on certain platforms.
- Designing flags and masks is a skill you will need for hardware-aware code, systems work, and embedded programming.
Practice in CodeSandbox
The sandbox below uses CodeSandbox's Universal starter. For C, the key learning loop is still compile and run in the terminal, so recreate the lesson code as a source file and repeat that cycle directly.
💬 댓글
이 글에 대한 의견을 남겨주세요