C
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Functions
-
Section 4: Arrays and Strings
-
Section 5: Pointers
-
Section 6: Memory Management
-
Section 7: Structures and Unions
-
Section 8: The Preprocessor and Build Process
-
Section 9: Standard Library: stdio.h
-
Section 10: Standard Library: stdlib.h
-
Section 11: Standard Library: string.h
-
Section 12: Standard Library: ctype.h and wctype.h
-
Section 13: Standard Library: math.h, complex.h, fenv.h, tgmath.h
-
Section 14: Standard Library: Type and Limit Headers
-
Section 15: Standard Library: Error Handling and Debugging
-
Section 16: Standard Library: Localization and Encoding
-
Section 17: Standard Library: time.h
-
Section 18: Standard Library: Concurrency (C11)
-
Section 19: POSIX and System Programming (unistd.h)
-
Section 20: More Data Structures
-
Section 21: Algorithms in C
-
Section 22: Bitwise Operations
-
Section 23: Command-Line Programs
-
Section 24: Debugging and Best Practices
-
Section 25: Compiler and Language Internals
-
Section 26: Embedded and Cross-Platform Considerations
-
Section 27: Networking Basics
-
Section 28: Practical Projects
-
Section 29: Interview Practice
-
Section 30: C23 Modern Features
-
Section 31: More Practice and Review
237: Common C Interview Questions on Bitwise Operations
I remember my first big technical interview for a systems role. The interviewer didn't ask me about high-level architecture; instead, they leaned in and asked, "Can you tell me if an integer is a power of two without using a loop or log2()?" I froze. I knew the math, but I forgot the bit-level trick. It’s a classic interview trope because it separates people who think in decimals from people who think in binary.
The "Power of Two" Trap
Let's pretend we're back in that interview. My first instinct is usually to just divide by two repeatedly until I hit one. But that's a loop, and the interviewer explicitly banned loops. So, I'll start playing with binary representations in my head. Let's look at some powers of two:
4: 0100
8: 1000
16: 10000
Notice a pattern? Only one bit is ever set. If I can find a way to check if exactly one bit is set, I've won. I try to think about what happens if I subtract 1 from these numbers. Let's test it in a quick snippet:
int x = 8;
printf("%d in binary: 1000\n", x);
printf("%d in binary: 0111\n", x - 1);
Interesting. Subtracting 1 flips the only set bit to 0 and turns all the trailing zeros into 1s. If I AND these two numbers together, what happens? 1000 & 0111 is 0000. Let's try it with a non-power of two, like 6:
int y = 6; // 0110
int z = 5; // 0101
// 0110 & 0101 = 0100 (Not zero!)
So the logic (x & (x - 1)) == 0 seems to work. But wait—I almost fell into the trap. What happens if x is 0? 0 & -1 is 0. My code would claim 0 is a power of two, which is mathematically wrong. I need to ensure x is greater than 0 first. The final "interview-ready" version is: return x > 0 && (x & (x - 1)) == 0;.
Wrestling with the Hamming Weight
Another common question is counting the "set bits" (the 1s) in an integer. This is often called the Hamming Weight. My immediate thought is to just shift the number 32 times and check the LSB (Least Significant Bit) each time.
int count = 0;
for (int i = 0; i < 32; i++) {
if ((n >> i) & 1) count++;
}
This works, but it's inefficient. If I have the number 1, I'm still looping 31 more times for zeros. It feels clunky. I remember seeing a trick called Brian Kernighan’s Algorithm. Let's see if I can reconstruct it. Remember how x & (x - 1) cleared the lowest set bit in the previous example? What if I use that in a loop?
Let's trace it with the number 12 (binary 1100):
- Iteration 1:
n = 12 (1100). I don = n & (n - 1). That's1100 & 1011, which results in1000. Count is now 1. - Iteration 2:
n = 8 (1000). I do1000 & 0111, which results in0000. Count is now 2. - Loop ends because
nis now 0.
This is significantly faster. Instead of 32 iterations, I only iterate as many times as there are 1s. In a production environment, you'd probably use the compiler builtin __builtin_popcount(n), but in an interview, showing you understand the n & (n - 1) trick proves you can manipulate bits effectively.
The XOR Swap Curiosity
You'll occasionally see the "Swap two integers without a temporary variable" question. It's mostly a party trick, but it demonstrates the XOR property: x ^ x = 0 and x ^ 0 = x.
I'll try it out:
int a = 5; // 0101
int b = 9; // 1001
a = a ^ b; // a is now 1100 (the "diff" between them)
b = a ^ b; // b is now 1100 ^ 1001 = 0101 (which was the original a)
a = a ^ b; // a is now 1100 ^ 0101 = 1001 (which was the original b)
It works! But here is a word of caution: never actually do this in real production code. It's harder to read than a simple temp variable, and if a and b happen to point to the same memory location (aliasing), you'll XOR the value with itself and end up with 0. The interviewer wants to see if you know XOR, not if you're willing to write dangerous code.
📋 Practical Task
Exercise: Building a Bit-Field Status Register
In embedded systems, we often pack multiple boolean flags into a single unsigned char to save space. Your task is to implement a small "Status Register" manager.
Write a program that defines the following bit positions:
READY_BIT: Bit 0ERROR_BIT: Bit 1BUSY_BIT: Bit 2SYNC_BIT: Bit 3
Implement the following functionality:
- Initialize a
status` variable to 0. - Set the
READY_BITandSYNC_BITto 1. - Check if the
ERROR_BITis set (it should be 0). - Toggle the
BUSY_BITto 1. - Clear the
READY_BITback to 0. - Print the final status byte in hexadecimal.
Ensure you use bitwise operators (|, &, ^, ~) for all modifications; do not use addition or subtraction.
There are no comments for now.