5.1.23 · D4C Programming

Exercises — Bit fields in structs

2,916 words13 min readBack to topic

Parent: Bit fields in structs. This page is a self-testing ladder. Each problem states the question cleanly; the Solution is hidden inside a collapsible callout so you can try first, then reveal. Levels climb from recognising the syntax to mastering real hardware-register layouts.

Before we start, one shared picture to lean on. A bit field lives inside a storage unit — think of an unsigned int as a row of numbered slots, filled by fields in these figures. Keep this in mind for every packing question.

Figure — Bit fields in structs

Recall the vault prerequisites if any word feels new: Data types and sizeof, Bitwise operators, Unsigned vs signed integers, Memory alignment and padding, Structs in C.


Level 1 — Recognition

Recall Solution — L1·Q1

Answer: (b).

  • (a) illegal — the type of a bit field must be one of the portable integer types permitted by the standard (signed int, unsigned int, _Bool). float is not.
  • (c) illegal — the width must be a non-negative integer constant; 2.5 is not an integer.
  • (d) illegal — a bit field is a single scalar, you cannot make an array a bit field.
  • (b) legal — type member : width; with an allowed integer type and an integer width. ✓
Recall Solution — L1·Q2

Use the counting rule: bits give combinations, largest is all-ones . Largest = 15.


Level 2 — Application

Recall Solution — L2·Q1

Why this step: we need , i.e. .

Answer: bits. Declaration: unsigned int val : 10; (holds ).

Recall Solution — L2·Q2

A 3-bit field keeps only the low 3 bits = value mod . Prints 4. No error, no warning — silent truncation. Look at figure s02: the top bits of 20 simply fall off the left edge of the 3-slot box.

Figure — Bit fields in structs
Recall Solution — L2·Q3

A signed field spends its top bit on the sign (see Unsigned vs signed integers). With bits in two's complement: Answer: −8 to 7 (16 values total, but only 0..7 are non-negative).


Level 3 — Analysis

Recall Solution — L3·Q1

Track the offset in one 32-bit unit (figure s03):

  • a takes bits 0–4 (offset now 5).
  • b takes bits 5–9 (offset now 10).
  • c needs 25 bits, but only remain → won't fit. It starts a fresh unit (bits 0–24 of unit #2).

Two storage units are used → bytes. sizeof(struct S) = 8 under these assumptions (32-bit unit, left-to-right packing). On a compiler with a different unit size or packing direction the number can differ — the reasoning, not the constant, is the point.

Figure — Bit fields in structs
Recall Solution — L3·Q2

unsigned : 0; is the align-to-next-unit instruction: it forces the field after it to begin a fresh storage unit.

  • low → bits 0–3 of unit #1.
  • : 0 → close unit #1, jump to unit #2.
  • high → bits 0–3 of unit #2.

Two units → sizeof(struct R) = 8 bytes (assuming 4-byte units). (Contrast: without the : 0 divider, both 4-bit fields fit in one unit → 4 bytes.) See Memory alignment and padding.

Recall Solution — L3·Q3

It does NOT compile. The smallest thing a C pointer can address is a byte. A 1-bit field may sit in the middle of a byte (e.g. bit 3 of byte 0), which has no byte address of its own. So &s.flag is a compile error. Fix — copy to a normal variable first:

unsigned t = s.flag;   // t is a full addressable variable
unsigned *p = &t;      // legal

Level 4 — Synthesis

Recall Solution — L4·Q1

Sum the widths: bits → the logical layout needs exactly one byte. In portable standard C the only allowed bit-field types are unsigned int / signed int / _Bool, so we declare with unsigned int (see Embedded systems / hardware registers):

struct StatusReg {
    unsigned int ready    : 1;   // bit 0
    unsigned int speed    : 3;   // bits 1..3
    unsigned int error    : 2;   // bits 4..5
    unsigned int reserved : 2;   // bits 6..7
};

All 8 logical bits are accounted for; speed correctly holds . ✓

Honest caveat about sizeof: because the storage unit is an unsigned int, sizeof(struct StatusReg) is typically 4 bytes, not 1 — the fields live in the low 8 bits of a 4-byte unit, the rest is padding. You cannot portably force a 1-byte struct: unsigned char ready : 1; is a compiler extension (GCC/Clang accept it, but the standard does not list char as a bit-field type, and even then it may still pack into an int on some targets). If you truly need an exact 1-byte hardware image, the portable route is a raw unsigned char plus manual shift-and-mask (next exercise), or a compiler-specific __attribute__((packed)).

Recall Solution — L4·Q2

speed sits at bits 1–3. Shift it down so bit 1 lands at position 0, then mask to keep 3 bits.

  • Shift right by 1: reg >> 1 brings bits 1–3 to positions 0–2.
  • Mask with : & 0x7 keeps exactly those 3 bits.
unsigned speed = (reg >> 1) & 0x7;

Check with reg = 0b01011010 (= 90): reg >> 1 = 0b00101101, & 0b111 = 0b101 = 5. So speed = 5. This shift-and-mask is fully portable — it does not depend on packing direction, unit size, or signedness the way a bit-field layout does.


Level 5 — Mastery

Recall Solution — L5·Q1

Each field is 4 bits → keep value mod .

  • s.a = 25: . (Binary: , low 4 bits .)
  • s.b = 7: , stored as-is .

Output: 9 7. Both fields live in the same storage unit (4+4 = 8 bits ≤ 32), independent of each other. (The values 9 and 7 are portable; which physical bits hold them depends on packing direction.)

Recall Solution — L5·Q2

Place each field at its starting bit and add (this is packing, done by hand — order fixed by the problem, so it's unambiguous):

  • ready (bit 0):
  • speed (bits 1–3):
  • error (bits 4–5):
  • reserved (bits 6–7):

Answer: 43. Reading right-to-left: reserved=00, error=10, speed=101, ready=1 — matches the inputs. ✓

Recall Solution — L5·Q3
  • p → bits 0–9 of unit #1 (offset 10).
  • q → bits 10–19 of unit #1 (offset 20).
  • : 0close unit #1, force next field to a fresh unit.
  • r → bits 0–9 of unit #2.

Even though r (10 bits) would have fit in unit #1's remaining 12 bits, the zero-width field pushed it to unit #2. Two units used → sizeof(struct M) = 8 bytes (under the 32-bit-unit assumption).


Recall Rapid recap — the four moves you practised

Truncation ::: value stored = assigned value mod (keep low N bits). Signed range ::: to ; unsigned range to . Packing rule ::: fill a storage unit until a field won't fit; then start a fresh unit. Extract a field at bit k ::: (reg >> k) & mask — shift first, then mask. Which parts are portable ::: values/widths/wrapping are; exact byte layout (packing order, unit size) is implementation-defined.

Connections

  • Parent: Bit fields in structs
  • Bitwise operators — the manual shift-and-mask behind every field.
  • Data types and sizeof — why the unit is an int and sizeof jumps in chunks.
  • Memory alignment and padding — the zero-width divider in action.
  • Embedded systems / hardware registers — the L4/L5 status-register pattern.
  • Unsigned vs signed integers — the lost sign bit in L2·Q3.
  • Structs in C — bit fields are just special struct members.