K&R C Exercise 2-6 — setbits(x, p, n, y)
Exercise 2-6: Write a function
setbits(x,p,n,y)that returnsxwith thenbits that begin at positionpset to the rightmostnbits ofy, leaving the other bits unchanged.
Approach
K&R numbers bit positions from the right starting at 0, so bit 0 is the least-significant bit. The job is to replace a window of n consecutive bits inside x — the window runs from bit p down to bit p+1-n — with the rightmost n bits of y, without touching any bit outside that window.
The operation takes two steps:
- Build a mask of
nones and position it over the target window. - Clear those bits in
xusing AND with the complement of the positioned mask, then OR in the rightmostnbits ofyshifted to the same position.
The key formula:
unsigned mask = ~(~0U << n); /* n ones at the low-order positions */
int shift = p + 1 - n; /* left edge of the window */
result = (x & ~(mask << shift)) | ((y & mask) << shift);
Why ~0U and not ~0? In C89/C90, left-shifting a negative signed integer is undefined behaviour. ~0 on most systems is a signed int equal to -1. Using the unsigned literal ~0U keeps the mask arithmetic fully defined regardless of the platform’s word width.
Worked example (8-bit illustration)
x = 0xF0 = 1111 0000 p = 5, n = 3, y = 0x05 = 0000 0101 mask = ~(~0U << 3) = 0000 0111 shift = 5 + 1 - 3 = 3 mask << shift = 0000 0111 << 3 = 0011 1000 ~(mask << 3) = 1100 0111 ← clearing mask x & ~(mask << 3) = 1111 0000 & 1100 0111 = 1100 0000 y & mask = 0000 0101 & 0000 0111 = 0000 0101 (y & mask) << 3 = 0000 0101 << 3 = 0010 1000 result = 1100 0000 | 0010 1000 = 1110 1000 = 0xE8
Bits 5–3 of x (which were 110) have been replaced by bits 2–0 of y (101), giving 0xE8. Every other bit of x is unchanged.
C Source Code
/* K&R Exercise 2-6 — setbits
* Compile: gcc -ansi -Wall ex2-6.c -o ex2-6 */
#include <stdio.h>
unsigned setbits(unsigned x, int p, int n, unsigned y);
int main(void)
{
printf("setbits(0xF0, 5, 3, 0x05) = 0x%02X\n", setbits(0xF0, 5, 3, 0x05));
printf("setbits(0xFF, 3, 4, 0x00) = 0x%02X\n", setbits(0xFF, 3, 4, 0x00));
printf("setbits(0x00, 7, 8, 0xFF) = 0x%02X\n", setbits(0x00, 7, 8, 0xFF));
return 0;
}
unsigned setbits(unsigned x, int p, int n, unsigned y)
{
unsigned mask = ~(~0U << n);
int shift = p + 1 - n;
return (x & ~(mask << shift)) | ((y & mask) << shift);
}
Compile and Run
gcc -ansi -Wall ex2-6.c -o ex2-6 && ./ex2-6
Sample Output
setbits(0xF0, 5, 3, 0x05) = 0xE8 setbits(0xFF, 3, 4, 0x00) = 0xF0 setbits(0x00, 7, 8, 0xFF) = 0xFF
The first call confirms the worked example. The second call clears all four bits of the window at positions 3–0 inside 0xFF (because y=0x00), turning 11111111 into 11110000 = 0xF0. The third call writes all eight bits of 0xFF into 0x00 starting at bit 7 — covering the entire byte — producing 0xFF.
What This Exercise Teaches
- Unsigned masks avoid undefined behaviour — always use
~0Uwhen building a mask by shifting, never~0on a signedint. - Mask construction from first principles —
~(~0U << n)produces exactlynones in the low-order positions without any hard-coded constant, which means the code adapts automatically to different word widths. - Two-step read-modify-write pattern — clear a field with
& ~mask, insert new bits with|. This pattern appears in every device-driver register write and network-protocol field-packing routine. - K&R bit-position convention — bit 0 is the rightmost. The window from bit
pdown to bitp+1-nis consistent withgetbitsearlier in Chapter 2, so these exercises build on each other. - Isolating bits of y before shifting —
y & maskstrips any high bits ofythat would otherwise corrupt bits outside the target window after the left shift.
Set Up Your C Environment
You can compile and run this code on any system with GCC. If you haven’t set up C yet for Chapter 2:
- Install GCC on Windows
- Install GCC on macOS —
xcode-select --installis enough - Install GCC on Linux
- VS Code for C Programming — recommended editor with integrated terminal
Book: The C Programming Language, 2nd Ed — Kernighan & Ritchie