What you'll learn
User-defined types let a C program speak in domain concepts instead of loose collections of primitives. Structures group simultaneous values, enums name choices, and unions represent alternative views of shared storage.
By the end, you'll be able to:
- Define, initialize, copy, and pass structure values
- Use dot and arrow member access correctly
- Model finite states with enums and variants with tagged unions
- Account for alignment and padding without depending on one layout
Define structures
A struct creates one type from named members that exist simultaneously. The definition describes a layout; each declared object receives its own member storage.
#include <stdio.h>
struct Student {
int id;
char name[40];
double average;
};
void print_student(const struct Student *student) {
printf("#%d %s — %.1f\n",
student->id, student->name, student->average);
}
int main(void) {
struct Student maya = {
.id = 7,
.name = "Maya",
.average = 93.5
};
print_student(&maya);
return 0;
}Initialize & copy
Designated initializers make member intent explicit and tolerate field reordering. Unlike arrays, complete structure objects may be assigned and passed by value.
#include <stdio.h>
typedef struct {
int x;
int y;
} Point;
int main(void) {
Point original = {.x = 3, .y = 4};
Point copy = original; // all members copied by value
copy.x = 10;
printf("original=(%d,%d) copy=(%d,%d)\n",
original.x, original.y, copy.x, copy.y);
return 0;
}Note
Pointers to structures
Use . with a structure value and -> with a pointer to a structure. The expression student->id is shorthand for(*student).id.
| Have | Member access | Example |
|---|---|---|
| struct value | . | student.id |
| pointer to struct | -> | student_ptr->id |
Tip
const when the function only reads their members.Nested models
Members may be arrays, structures, pointers, enums, or other object types. Nesting small, meaningful types keeps complex models understandable.
#include <stdio.h>
typedef struct { double latitude, longitude; } Coordinate;
typedef struct {
char name[40];
Coordinate location;
} City;
int main(void) {
City beirut = {
.name = "Beirut",
.location = {.latitude = 33.8938, .longitude = 35.5018}
};
printf("%s: %.4f, %.4f\n",
beirut.name, beirut.location.latitude, beirut.location.longitude);
return 0;
}Enumerations
An enum gives names to integral constants. It improves readability and works naturally withswitch, although C still permits values outside the listed enumerators.
#include <stdio.h>
typedef enum {
STATUS_PENDING,
STATUS_RUNNING,
STATUS_DONE,
STATUS_FAILED
} Status;
const char *status_name(Status status) {
switch (status) {
case STATUS_PENDING: return "pending";
case STATUS_RUNNING: return "running";
case STATUS_DONE: return "done";
case STATUS_FAILED: return "failed";
}
return "invalid";
}
int main(void) {
printf("%s\n", status_name(STATUS_DONE));
return 0;
}Watch out
Unions & tagged unions
Union members overlap the same storage. Only the member representing the currently stored alternative should be read. Pairing a union with an enum tag makes that state explicit.
#include <stdbool.h>
#include <stdio.h>
typedef enum { VALUE_INT, VALUE_DOUBLE } ValueKind;
typedef struct {
ValueKind kind;
union {
int as_int;
double as_double;
} data;
} Value;
void print_value(Value value) {
if (value.kind == VALUE_INT) {
printf("%d\n", value.data.as_int);
} else {
printf("%.2f\n", value.data.as_double);
}
}
int main(void) {
Value answer = {.kind = VALUE_INT, .data.as_int = 42};
print_value(answer);
return 0;
}Key idea
typedef & API types
typedef creates an alias for an existing type. It does not create a runtime object or a fundamentally distinct type, but it can make public interfaces much clearer.
| Good use | Example |
|---|---|
| Name a domain structure | typedef struct Point Point; |
| Name a callback signature | typedef void (*Handler)(int); |
| Hide an implementation | typedef struct Database Database; |
Layout & padding
Implementations may insert padding so each member meets its alignment requirement and array elements remain correctly aligned.
tag : char
1 byte
padding
3 bytes
count : int
4 bytes
A common layout—not a portable byte-level promise.
#include <stddef.h>
#include <stdio.h>
struct Record {
char tag;
int count;
};
int main(void) {
printf("tag offset: %zu\n", offsetof(struct Record, tag));
printf("count offset: %zu\n", offsetof(struct Record, count));
printf("total size: %zu\n", sizeof(struct Record));
return 0;
}Watch out
Recap & quick check
Key takeaways
- Structures group simultaneous named members and support whole-object assignment.
- Dot accesses a value's member; arrow accesses through a pointer.
- Enums name integral states but external values still require validation.
- A tagged union pairs a discriminator with one active representation.
- Alignment may insert padding, so raw layouts are not portable serialization formats.
Quick check
1. Which operator accesses a member through a structure pointer?
2. What happens during structure assignment?
3. Why does a tagged union include an enum?
4. Why may sizeof(struct Record) exceed member-size totals?
Your data now has domain structure. Next up: Module 12 — Preprocessor, Headers & Modular Programs, where one source file becomes a maintainable project.