2026-07-12
This helper builds a bitmask with the lowest n bits set — used everywhere in packet parsers, hash tables, and bit-field extractors. It's ten lines of code that has shipped in production for years. Then someone calls it with n = 32 and the mask becomes zero.
#include <stdio.h>
#include <stdint.h>
// Return a bitmask with the lowest `n` bits set.
// low_bits(4) -> 0x0000000f
// low_bits(16) -> 0x0000ffff
// low_bits(32) -> 0xffffffff (we hope)
uint32_t low_bits(int n) {
return (1U << n) - 1;
}
uint32_t extract(uint32_t value, int nbits) {
return value & low_bits(nbits);
}
int main(void) {
printf("extract(0xDEADBEEF, 4) = 0x%08x\n", extract(0xDEADBEEF, 4));
printf("extract(0xDEADBEEF, 16) = 0x%08x\n", extract(0xDEADBEEF, 16));
printf("extract(0xDEADBEEF, 31) = 0x%08x\n", extract(0xDEADBEEF, 31));
printf("extract(0xDEADBEEF, 32) = 0x%08x\n", extract(0xDEADBEEF, 32));
return 0;
}
The first three lines print what you'd expect. The last one prints 0x00000000. An identity operation just annihilated your value.
C11 §6.5.7 says: "If the value of the right operand is negative or is greater than or equal to the width of the promoted left operand, the behavior is undefined." The width of unsigned int on virtually every modern platform is 32, so 1U << 32 is undefined behavior — not implementation-defined, not "returns zero", but undefined.
Why does it usually appear to "return 1" (making the mask 1 - 1 == 0)? Because x86's SHL instruction masks the shift count against the operand width: count & 0x1F for 32-bit operands. So the CPU actually computes 1 << (32 & 31), which is 1 << 0 = 1. ARM64's LSL saturates instead and yields 0. RISC-V does something else. And a smart optimizer, having deduced n < 32 from the UB, may propagate that fact into surrounding code — deleting bounds checks you thought were live.
The trap survives review because the boundary case is often unreachable in tests. low_bits(31) works. low_bits(30) works. Nobody writes a test for the exact width because "obviously that's the max useful value" — which is exactly when the compiler stops being your friend.
Widen the shift so the boundary case has room to breathe, then narrow:
uint32_t low_bits(int n) {
return (uint32_t)((1ULL << n) - 1);
}
unsigned long long is guaranteed at least 64 bits, so 1ULL << 32 is fully defined and equals 0x100000000. Subtracting one gives 0xFFFFFFFF, and the cast truncates cleanly. This works for n in [0, 64); if you need n == 64 for a 64-bit mask, guard it explicitly:
uint64_t low_bits64(int n) {
return n == 64 ? UINT64_MAX : (1ULL << n) - 1;
}
A branch-free alternative that avoids the guard is ~UINT32_C(0) >> (32 - n) — but that has its own boundary problem when n == 0. There's no shift-only expression that handles both edges cleanly; you either widen, or you branch.
