C Basics · beginner · ~12 min
Flip the bit order of a word or byte.
Reversing a word's bit order — bit 0 becomes bit 31, bit 1 becomes bit 30, and so on — is a small exercise with a big lesson in loop invariants. The straightforward method builds the answer one bit at a time: shift the result left to make room, OR in the low bit of the input, then shift the input right. After exactly width iterations the input is exhausted and the result holds the mirrored value. The subtlety is that the loop must run the full width even when the input reaches zero early, or the remaining shifts never happen and the answer comes out shifted toward the wrong end.
Bit order is a real interoperability concern. Some serial protocols transmit least-significant-bit first while your data is most-significant-bit first; certain CRC variants are defined over reflected input and output; graphics and DSP code reverse index bits for FFT butterflies. When two systems disagree about bit order the data is not corrupted so much as mirrored, which is confusing to debug unless you recognise the pattern.
Build from the bottom up. Start with r = 0. Each iteration: r = (r << 1) | (x & 1u); then x >>= 1;. The first input bit read ends up pushed furthest left by the remaining shifts, which is exactly the reversal.
The fixed trip count. Loop exactly 32 times for a 32-bit value — not while (x). If the input's high bits are zero, an early exit leaves the result under-shifted, so reverse(1) would return 1 instead of 0x80000000.
Why unsigned. x >>= 1 on a signed negative value sign-extends, feeding 1s in from the left forever. uint32_t makes the shift a plain logical shift.
Self-inverse. Reversing twice returns the original value — a free correctness check to assert in tests.
Faster alternatives exist. Production code often uses a byte lookup table or a divide-and-conquer swap (swap odd/even bits, then pairs, then nibbles, then bytes) in O(log n) steps. Understand the simple loop first; it is the reference implementation you test the clever one against.
#include <stdint.h>
uint32_t reverse_bits(uint32_t x) {
uint32_t r = 0;
for (int i = 0; i < 32; i++) { // fixed 32 iterations — not `while (x)`
r = (r << 1) | (x & 1u); // make room, take the low bit
x >>= 1; // consume it
}
return r;
}
Key points:
uint32_t (not int) so >> is a logical shift.reverse_bits(reverse_bits(x)) == x — assert this in your tests.Reversing bits maps bit i to bit n-1-i. The straightforward method shifts a result left while feeding in the source's low bit, n times. Reversal is an involution: doing it twice returns the original.
The demo reverses a 32-bit value and checks the round-trip.
#include <stdio.h>
#include <stdint.h>
static uint32_t reverse_bits(uint32_t x){ uint32_t r=0; for(int i=0;i<32;i++){ r=(r<<1)|(x&1u); x>>=1; } return r; }
int main(void){
uint32_t x = 0x0000000Fu;
printf("0x%08X reversed = 0x%08X\n", x, reverse_bits(x));
printf("involution check: 0x%08X\n", reverse_bits(reverse_bits(0x12345678u)));
return 0;
}
| Step | Line | What happens |
|---|---|---|
| 1 | uint32_t r = 0; |
The accumulator starts empty. |
| 2 | r = (r << 1) | (x & 1u) |
Iteration 1 with x = 1: r becomes 1; the bit that will travel furthest left enters first. |
| 3 | x >>= 1 |
x becomes 0 — but the loop keeps running. |
| 4 | iterations 2–32 | r <<= 1 thirty-one more times, walking that single 1 up to bit 31. |
| 5 | return | reverse_bits(1) → 0x80000000. A while (x) loop would have stopped at step 3 and wrongly returned 1. |
Stopping the loop when x reaches zero instead of running the full 32 iterations — reverse(1) then returns 1 rather than 0x80000000, because the remaining left-shifts never happen. Reversing a signed value, so >> sign-extends and feeds in 1s. Confusing bit reversal with byte-order swapping (htonl) — they are different operations.
Compiler errors and warnings:
warning: right shift count >= width of type — your loop bound exceeds 32.-Wsign-compare between int i and an unsigned bound; keep the counter int and the bound a literal.Runtime symptoms:
reverse(1) returns 1. You used while (x) instead of a fixed 32 iterations — the classic bug here.x is a signed negative value; >> is sign-extending. Use uint32_t.r after ORing instead of before, or shifted x before reading its low bit. Order is: shift r, OR in x & 1, then shift x.uint8_t but printed it as 32-bit, or looped 8 times on a 32-bit value.Technique: test the identities first — reverse(0) == 0, reverse(0xFFFFFFFF) == 0xFFFFFFFF, reverse(1) == 0x80000000, and reverse(reverse(x)) == x for random x.
>> on a signed negative value is implementation-defined (arithmetic in practice), which breaks the algorithm. Use uint32_t throughout.uint64_t, or 64 on a uint32_t, silently produces wrong answers — the second case is also an undefined over-width shift. Tie the trip count to the type (sizeof(x) * CHAR_BIT).uint8_t by this loop promotes to int; mask back with & 0xFFu and loop only 8 times, or the result carries stray high bits.Concrete uses: CRC-32 as used by Ethernet and zlib is defined over reflected input and output, so implementations reverse bits (or bake the reflection into the table). Some SPI and I²C peripherals clock out LSB-first while the host stores MSB-first. FFT implementations permute samples by reversed index. Barcode and RFID encodings sometimes specify reversed bit order.
Professional best practices:
Beginner:
Intermediate:
sizeof(x) * CHAR_BIT so the function cannot silently mismatch its type.htonl); they are different operations and mixing them up produces very confusing bugs.1. (Beginner) Reverse 32 bits. Implement uint32_t reverse_bits(uint32_t x) with the fixed-count loop. Example: 1 → 0x80000000; 0x80000000 → 1. Concepts: accumulate-and-shift, fixed trip count.
2. (Beginner) Prove the self-inverse. Write a test asserting reverse_bits(reverse_bits(x)) == x for 1,000 pseudo-random values plus 0, 1 and 0xFFFFFFFF. Concepts: property testing.
3. (Intermediate) Reverse a byte. Implement uint8_t reverse_byte(uint8_t b) looping 8 times, and confirm 0b00000001 → 0b10000000. Hint: mask the result back to 8 bits after promotion. Concepts: integer promotion, width discipline.
4. (Intermediate) Table-driven reversal. Precompute a 256-entry table of reversed bytes, then reverse a uint32_t as four table lookups in swapped order. Requirements: must agree with the loop version for every tested input. Concepts: lookup tables, validating an optimisation against a reference.
Reversal builds the answer one bit at a time — shift the result left, OR in the input's low bit, shift the input right — repeated for exactly the type's width. The fixed trip count is the crux: exiting early when the input hits zero leaves the result under-shifted, so reverse(1) must run all 32 passes to become 0x80000000. Keep the value uint32_t so the right shift is logical rather than sign-extending, assert the self-inverse property in tests, and treat the simple loop as the reference you validate any faster table-driven version against.