5.1.23 · D5C Programming
Question bank — Bit fields in structs
Deep-dive child of Bit fields in structs. This page is all traps: the questions that sound obvious but hide a misconception or a boundary case. Read the question, say your answer out loud, then reveal.
Before you start, one picture in your head: an unsigned int is a row of 32 tiny boxes. A bit field claims some of those boxes and nothing more. Every trap below comes from forgetting one of three facts:
- The field is only as wide as you asked — extra digits fall off (see Bitwise operators for the truncation mechanics).
- A field may sit in the middle of a byte, so it has no address.
- Almost every layout detail (order, base type signedness, straddling) is implementation-defined — the standard deliberately leaves it to the compiler.
Keep Unsigned vs signed integers and Data types and sizeof open in another tab; several traps lean on them.
True or false — justify
A struct with three 1-bit fields uses only 1 byte of memory.
False. The compiler allocates a whole storage unit (typically a 4-byte
int) to hold them, so sizeof is usually 4. The saving is versus three separate ints (12 bytes), not versus 1 byte.unsigned x : 8; and unsigned char x; are interchangeable.
False. The bit field lives packed inside a shared storage unit and has no address, while the
unsigned char is a normal, addressable variable. They also differ in how neighbouring members pack.A 3-bit signed field can store the value 5.
False. A signed 3-bit field ranges −4 to 3, because the top bit is the sign bit. 5 would overflow; only
unsigned int : 3 reaches 0..7.Assigning 300 to a 4-bit field is a compile-time error.
False. It compiles silently and truncates to the low 4 bits: . The compiler never range-checks bit-field assignments.
You can always take the address of any struct member with &.
False. Bit fields are the exception: they may not begin on a byte boundary, so
&s.field is a compile error. Copy into a normal variable first.unsigned : 0; reserves zero bits, so it does nothing.
False. A zero-width unnamed field is a deliberate instruction: it forces the next field to start at the beginning of a fresh storage unit (an alignment tool — see Memory alignment and padding).
The order in which fields fill the storage unit (low bits first vs high bits first) is fixed by the C standard.
False. Allocation order is implementation-defined. Portable code must not assume a physical bit position.
float value : 4; is a valid bit field if you only need 4 bits.
False. Bit fields must use an integer type (
int, signed/unsigned int, _Bool in C99). Floating-point types are not allowed.Two structs with identical bit-field declarations always have the same memory layout on any compiler.
False. Bit order, straddling behaviour, and unit choice are all implementation-defined, so the same source can lay out differently across compilers or platforms.
A 1-bit signed field can hold the value 1.
False (or implementation-dependent, and dangerous). A 1-bit signed field has one sign bit and zero magnitude bits; its representable values are typically 0 and −1, not 0 and 1. Use
unsigned : 1 for a 0/1 flag.Spot the error
struct { unsigned f : 1; } s;
unsigned *p = &s.f;
``` ::: `&s.f` is illegal — a bit field has no address. **Fix:** `unsigned t = s.f; unsigned *p = &t;` — copy into a real variable first.
```c
struct T { double d : 10; };
``` ::: `double` is not an integer type, so it cannot be a bit field. **Fix:** use an integer base type like `unsigned int d : 10;`.
```c
struct T { unsigned x : 3; };
struct T s; s.x = 8;
printf("%u", s.x); // expects 8
``` ::: A 3-bit field maxes at 7. `8 = 1000₂` truncates to the low 3 bits `000₂ = 0`, so it prints **0**, not 8. No warning is given.
```c
int flag : 1; // want a 0/1 flag
``` ::: Two problems: plain `int` bit-field signedness is implementation-defined, and a signed 1-bit field can't reliably hold 1. **Fix:** `unsigned int flag : 1;`. Also, bit fields must live inside a struct, not stand alone.
```c
struct { unsigned a : 5, b : 5, c : 25; } s;
// programmer assumes sizeof == 4
``` ::: `a`+`b` use 10 bits; `c` needs 25 more but only 22 remain in the first unit, so `c` starts a **new** storage unit → `sizeof` is typically **8**, not 4.
```c
struct { unsigned a : 40; };
``` ::: A field's width may not exceed the width of its storage type (typically 32 bits for `unsigned int`). Asking for 40 bits is a constraint violation → compile error.
```c
struct P { unsigned x : 3; } arr[10];
printf("%p", (void*)&arr[2].x);
``` ::: Same trap dressed up in an array: `&arr[2].x` is still taking the address of a bit field → compile error. Copy it out first.
---
## Why questions
Why must bit fields use an integer type and not, say, `char[]` or `float`? ::: Bit-field semantics are defined only for integer bit patterns — a *width in bits* of a whole number of value bits. Floats have exponent/mantissa structure and arrays have addresses, neither of which fits the "N packed bits" model.
Why can't you take the address of a bit field? ::: C addresses are byte-granular, but a bit field can start partway through a byte. There is no byte address that names "just those 3 bits," so `&` is forbidden.
Why does overflow in a bit field wrap silently instead of erroring? ::: Assignment stores the value modulo $2^N$ (the field only *has* N bits, so higher bits have nowhere to go). C treats this as normal unsigned wraparound, not an error — the same philosophy as `unsigned int` overflow.
Why declare `unsigned` explicitly instead of plain `int` for a bit field? ::: For plain `int` bit fields the standard leaves signedness ==implementation-defined==. Writing `unsigned`/`signed` explicitly guarantees the range you expect (0..$2^N-1$ vs $-2^{N-1}..2^{N-1}-1$).
Why would `sizeof` still be 4 for a struct that uses only 3 bits total? ::: The compiler allocates whole storage units (an `int` box), and structs are padded to their alignment. Three bits round up to a full unit; you can't have a 3-bit-sized object.
Why are bit fields especially useful for [[Embedded systems / hardware registers]]? ::: A hardware register is a fixed byte/word where individual bits mean specific things (bit 0 = ready, bits 1–3 = speed). A struct of bit fields mirrors that layout directly, so `reg.speed = 3;` is clearer and safer than manual `<< & |` masking.
Why does `unsigned : 0;` exist at all? ::: It gives you explicit control over alignment: it flushes the current storage unit so the next field starts fresh, matching a hardware boundary or a documented file/packet format.
---
## Edge cases
What is the range of a **0-width named** field like `unsigned z : 0;`? ::: This is illegal — a *named* zero-width field is a constraint violation. Only an **unnamed** `unsigned : 0;` is allowed, and it stores nothing (it's just an alignment marker).
What can a `_Bool : 1;` field store? ::: Exactly 0 or 1 (C99 `_Bool`). Any nonzero assignment normalises to 1, unlike an `unsigned : 1` where you truly get the low bit — a cleaner choice for genuine true/false flags.
Does a wider-than-needed field ever *hurt*? ::: Not in correctness, but it wastes packing space: `unsigned flag : 8;` for a 0/1 value burns 7 unusable bits and may push later fields into a new unit, growing `sizeof`.
What happens at exactly the top value, e.g. a 3-bit field set to 7? ::: Perfectly fine — `7 = 111₂` fills all 3 bits with no overflow. Trouble begins only at 8 (`1000₂`), the first value that needs a 4th bit.
Can a bit field straddle two storage units (e.g. last 2 bits of one int + first 3 of the next)? ::: Whether straddling is allowed is ==implementation-defined==. Some compilers pack across the boundary; others insert padding and start the field fresh. Never rely on a specific behaviour.
Is the number of bits in the storage unit (`int`) guaranteed to be 32? ::: No. It's whatever the platform's `int` is (at least 16 bits by the standard). Portable code checks with [[Data types and sizeof]] and avoids assuming 32.
What if you assign a negative number to an `unsigned` bit field? ::: It wraps modulo $2^N$ just like `unsigned` arithmetic: e.g. `-1` into a 3-bit field stores `111₂ = 7`. No error; the two's-complement low bits are kept.
---
> [!recall]- One-line summary of every trap
> Fields are exactly N bits (extras fall off), have no address, and almost every layout detail is implementation-defined — so declare `unsigned`, range-check yourself, and never assume physical bit order.
## Connections
- [[Bit fields in structs (index 5.1.23)|Parent: Bit fields in structs]]
- [[Structs in C]] — bit fields are special members.
- [[Bitwise operators]] — the manual mask/shift alternative and the truncation mechanism.
- [[Data types and sizeof]] — storage-unit size and `sizeof` surprises.
- [[Memory alignment and padding]] — the `: 0` alignment trick.
- [[Embedded systems / hardware registers]] — the register-mirroring use case.
- [[Unsigned vs signed integers]] — the lost sign bit and wraparound.