Bit shifting in C and C++ moves an integer’s bits left or right with the << and >> operators. A C++ bit shift is easiest to verify with a fixed-width unsigned value: left shifts add zero bits on the right, while unsigned right shifts add zero bits on the left. The important boundaries are the promoted type’s width, the shift count, and whether the operand is signed.
How does bit shifting in C use << and >>?
The expression value << count moves each bit toward a more significant position. Bits that leave the type are discarded. The expression value >> count moves bits toward less significant positions. For an unsigned operand, zero bits enter from the left.
For unsigned values, shifting left by n positions is equivalent to multiplying by 2n when the result remains within the available width. Shifting right by n positions is equivalent to dividing by 2n and discarding the remainder. A shift is not a rotation: discarded bits do not reappear at the other end.
How do binary traces explain a C++ bit shift?
These examples use 8-bit storage to make the movement visible. The binary notation shows the stored low eight bits.
Left shift: uint8_t x = 0x2D; starts as 00101101. After x << 2, the trace is:
00101101 << 2 = 10110100
The value changes from decimal 45 to decimal 180. The two zeros entering on the right replace the two high bits that fall off the left.
Unsigned right shift: uint8_t x = 0xB4; starts as 10110100. After x >> 2, the trace is:
10110100 >> 2 = 00101101
The result is decimal 45. In actual C and C++ expressions, small integer types undergo integer promotion first. If int can represent every uint8_t value, x is promoted to int; assigning the result back to uint8_t stores only the low eight bits.
How do shifts create masks, fields, and powers of two?
A shift creates a single-bit mask efficiently. With a 32-bit unsigned value, UINT32_C(1) << 5 produces 0x00000020, which selects bit 5. This represents 25. The count must stay within the valid range for the operand’s promoted type.
To create a mask for the lowest four bits, use (UINT32_C(1) << 4) – 1, producing 0x0000000F. To extract an eight-bit field beginning at bit 8, use:
(word >> 8) & UINT32_C(0xFF)
The right shift moves the field to the low end, and the mask removes unrelated bits. To insert a bounded field, mask the source value before shifting it, then combine it with the destination using bitwise OR.
How do signedness, width, and shift counts affect results?
Both operands undergo integer promotion, and the result type is the promoted type of the left operand. Therefore, the width that controls a shift is not always the declared width. A uint8_t commonly promotes to int, so its shift count is checked against int’s width rather than eight bits. A uint32_t normally remains an unsigned 32-bit type because int cannot represent all its values.
The shift count must be nonnegative and less than the bit width of the promoted left operand. A count equal to that width, or larger, produces undefined behavior in C and C++. Validate a runtime count before shifting; for a 32-bit value, a signed count must satisfy count >= 0 && count < 32.
Unsigned left shifts have defined modulo behavior: high bits are discarded. Signed left shifts are riskier because a result that cannot be represented can cause undefined behavior. Convert to an appropriately sized unsigned type when that modulo behavior is intended.
Right-shifting an unsigned value is logical and fills with zeros. Right-shifting a signed negative value is not a portable C shortcut: C makes that result implementation-defined, and language-version differences matter in C++. Use unsigned operands when the bit pattern, rather than an arithmetic sign, must be preserved.
