C · 2026-06-16 · embedded · bitwise · bit-manipulation · register

Bitwise operations (C)

Bit twiddling is half of register-level firmware, so it pays to use a vocabulary that is correct by construction. Two rules cover most of the bugs: always work on unsigned types — shifting a 1 into the sign bit of a signed int is undefined behaviour — and parenthesise every macro argument, because & binds looser than ==.

#include <stdint.h>
#include <stdbool.h>

/* Canonical single-bit vocabulary. The 1u is unsigned on purpose; the extra
   parentheses survive any expression passed as w or n. */
#define BIT(n)            (1u << (n))
#define BIT_SET(w, n)     ((w) |=  BIT(n))
#define BIT_CLEAR(w, n)   ((w) &= ~BIT(n))
#define BIT_TOGGLE(w, n)  ((w) ^=  BIT(n))
#define BIT_TEST(w, n)    (((w) >> (n)) & 1u)

Multi-bit fields and useful idioms

Single bits are the easy case. A register field a few bits wide is a read-modify-write: mask the old field out, OR the new value in. The constant-time tricks below all lean on one identity — word & (word - 1) clears the lowest set bit.

/*******************************************************************************
 * Function Name  : bitfield_set
 * Description    : Replace a width-bit field at bit pos, leaving the rest intact.
 * Input          : word - original value; pos - field LSB; width - field size;
 *                  value - new field value.
 * Output         : None.
 * Return         : word with the field updated.
 *******************************************************************************/
static inline uint32_t bitfield_set(uint32_t word, uint32_t pos, uint32_t width, uint32_t value)
{
  uint32_t mask = ((1u << width) - 1u) << pos;

  return (word & ~mask) | ((value << pos) & mask);
}

/*******************************************************************************
 * Function Name  : bitfield_get
 * Description    : Extract a width-bit field at bit pos, right-aligned to bit 0.
 * Input          : word - source value; pos - field LSB; width - field size.
 * Output         : None.
 * Return         : The field value shifted down to bit 0.
 *******************************************************************************/
static inline uint32_t bitfield_get(uint32_t word, uint32_t pos, uint32_t width)
{
  return (word >> pos) & ((1u << width) - 1u);
}

/*******************************************************************************
 * Function Name  : count_set_bits
 * Description    : Population count via Kernighan's method — one pass per set bit.
 * Input          : word - value to inspect.
 * Output         : None.
 * Return         : Number of bits set to 1.
 *******************************************************************************/
static inline uint32_t count_set_bits(uint32_t word)
{
  uint32_t count = 0u;

  while (word != 0u)
  {
    word &= (word - 1u);       /* clear the lowest set bit */
    count++;
  }

  return count;
}

/*******************************************************************************
 * Function Name  : is_power_of_two
 * Description    : True when exactly one bit is set (and word is non-zero).
 * Input          : word - value to test.
 * Output         : None.
 * Return         : true if a power of two, false otherwise.
 *******************************************************************************/
static inline bool is_power_of_two(uint32_t word)
{
  return (word != 0u) && ((word & (word - 1u)) == 0u);
}

Gotchas

  • Precedence. if (reg & MASK == 0) does not test what it looks like — == binds tighter than &, so it parses as reg & (MASK == 0). Write if ((reg & MASK) == 0u).
  • Signed shifts. 1 << 31 overflows a 32-bit signed int (UB); 1u << 31 is fine. Right-shifting a negative value is implementation-defined — keep everything unsigned.
  • Width 32. (1u << 32) is undefined, so the field helpers above assume width < 32; for a full-word mask use 0xFFFFFFFFu directly.
  • Let the hardware help. For population count and bit scanning, most Cortex-M cores have CLZ/RBIT; __builtin_popcount and __builtin_clz map straight to them when available and beat the portable loop. Use the loop only where the intrinsic isn’t.