C
Structs & Unions
An array groups many values of the same type. A struct is the opposite: it groups
several values of different types into one new, custom type — a sensor reading's timestamp,
value, and status flag, bundled as a single unit instead of three separate loose variables. A
union looks similar but means something almost opposite, covered second.
- What it is
struct: a custom type bundling several named fields, each with its own type and its own storage.union: several named fields sharing the exact same storage.- Mental model
- A
structis a form with several separate labeled boxes to fill in. Aunionis one box with several different labels you can read it under — only one label's data is valid at a time. - Key numbers
- A struct's size is roughly the sum of its fields' sizes (plus possible padding); a union's size is exactly its largest field's size, no more.
- Reach for it when
structwhenever several values conceptually belong together (a point's x/y, a sensor reading's fields);unionspecifically when you need to interpret the same bytes more than one way, never both at once.
Declaring and using a struct
struct SensorReading {
int timestamp;
float value;
char status;
};
struct SensorReading r1 = {1000, 23.5f, 'O'}; // O for "OK"
printf("%d\n", r1.timestamp); // 1000 — the DOT operator accesses a field
printf("%.1f\n", r1.value); // 23.5
r1.status = 'E'; // fields are ordinary, assignable variables
Each field is accessed with the dot operator (.) — r1.value
reads exactly the way it sounds: "r1's value field."
A struct's memory layout
Unlike a union, every field in a struct gets its own separate storage,
laid out one after another:
status has zero effect on timestamp or value.Structs with arrays, and arrays of structs
struct SensorReading history[10]; // an array of 10 whole structs
history[0].timestamp = 1000;
history[0].value = 23.5f;
history[3].status = 'E';
for (int i = 0; i < 10; i++) {
printf("%d: %.1f\n", history[i].timestamp, history[i].value);
}
This combination — an array of structs — is one of the most common real data structures in C: a fixed history of readings, a table of records, a list of connected devices.
Unions
A union looks exactly like a struct syntactically, but every field shares
the exact same memory — writing one field overwrites whatever was in every other field,
because there's only ever one shared block of storage underneath:
union Value {
int as_int;
float as_float;
char as_bytes[4];
};
union Value v;
v.as_int = 65;
printf("%d\n", v.as_int); // 65
printf("%d\n", v.as_bytes[0]); // some byte of 65's representation — as_int and as_bytes are THE SAME memory
Real-world applications
- Sensor/GPS records — bundling a timestamp, a value, and a status/quality flag together is exactly the Sensors domain's typical record shape.
- Network packet headers — a struct with fields matching a protocol's exact header layout, one field per header value.
- Type-punning with a union — reinterpreting the same 4 bytes as either a
floator its raw bit pattern, a common low-level trick — see Embedded C for where this gets genuinely important (and genuinely dangerous) on real hardware.
Gotchas
- A struct's total size is often larger than the sum of its fields, due to compiler-inserted padding for alignment — never assume a struct's size without checking
sizeof. Full treatment in Embedded C's Alignment & Padding chapter. - Copying a struct with
=(r2 = r1;) copies every field's value — this works fine for simple structs, but copies pointers as raw addresses, not the data they point to, which can cause two structs to unexpectedly share the same pointed-to memory. - Reading a union field that wasn't the last one written is technically undefined behavior in strict standard C, even though it "works" (and is even explicitly relied on) in practice on most real compilers — a well-known, common exception to how strictly the standard is usually followed.
- Forgetting the
structkeyword when declaring a variable (SensorReading r1;instead ofstruct SensorReading r1;) is a compile error in plain C — this is exactly the kind of frictiontypedefexists to remove, letting you write justSensorReading r1;once set up.