Structs
Bundling related values into a single named type
A program's data rarely comes in lone variables. A point has an x
and a y. A student has a name, an id, and a GPA. A date has a
year, month, and day. C's tool for bundling related values into a
single named thing is the struct.
Without structs you'd be passing around parallel arrays or huge parameter lists and praying nothing fell out of sync. With structs, each "thing" becomes a single, named, typed value you can move around as a unit.
Declaring a struct
struct Point {
int x;
int y;
};This declares a type called struct Point with two fields
(also called members): x and y. It does not yet create any
variables. To create one:
struct Point p;
p.x = 3;
p.y = 4;Field access uses a dot. You can also initialize at declaration:
struct Point p = {3, 4}; // positional
struct Point q = { .x = 3, .y = 4 }; // designated (clearer)Designated initializers are usually worth the extra characters — they make the code readable and survive field reordering.
typedef to drop the struct keyword
Writing struct Point over and over is noisy. The idiomatic remedy
is a typedef:
typedef struct {
int x;
int y;
} Point;
Point p = { .x = 3, .y = 4 };Now Point is the type name. You'll see both styles in real code;
the typedef'd form is more common in beginner-friendly tutorials and
in modern C code.
Passing structs to functions
You can pass a struct by value (a copy is made):
int dist_sq(Point p) {
return p.x * p.x + p.y * p.y;
}Or, for bigger structs or when you want to modify the original, pass a pointer:
void shift(Point *p, int dx, int dy) {
(*p).x += dx;
(*p).y += dy;
}That (*p).x is so common that C has a special operator for it:
the arrow ->.
void shift(Point *p, int dx, int dy) {
p->x += dx;
p->y += dy;
}p->x means "the field x of the struct that p points at". It's
exactly equivalent to (*p).x but easier to read.
| Through a value | Through a pointer |
|---|---|
p.x | p->x or (*p).x |
A bigger example: a student record
Things to notice:
- An array of structs stores them back-to-back in memory, the same way an array of ints does.
- The string fields are fixed-size character arrays — they live inside the struct.
const Student *ssays "I want a pointer to a student, and I promise not to modify it". Theconstis documentation enforced by the compiler.
Memory layout of a struct
A struct's fields are stored in declaration order, with possible padding between them so each field is properly aligned for its type:
sizeof(Student) will be at least 4 + 32 + 4 = 40 bytes, and may
be slightly larger because of alignment. Use sizeof rather than
hardcoding numbers.
Nested structs
Structs can contain other structs as fields. This is how you build up rich data without leaving C's type system.
Read box.top_left.x as "the x of the top_left of box". Dot
chains work just like in everyday English.
Struct assignment copies
Assigning one struct to another copies every field:
Point a = {1, 2};
Point b = a; // b is now its own (1, 2)
b.x = 99; // a is unchangedThis is convenient but be aware: copying a struct that contains a pointer copies the pointer, not the thing it points to. Both copies would then "own" the same heap allocation, which is a recipe for double-frees. We'll meet this pattern again in the linked-list chapter.
Challenge: rectangle area
Define a Rect struct with integer fields width and height. Write a function int area(const Rect *r) that returns r->width * r->height. The provided main builds a 4x5 rectangle and prints the area.
The program must print exactly 20.
Given Point *p = &some_point;, which expression accesses the field x?
p.x
*p.x
p->x
&p.x
What does this code print?
typedef struct { int a; int b; } S;
S x = {1, 2};
S y = x;
y.a = 99;
printf("%d %d\n", x.a, y.a);
Hint: the printf prints x.a first, then y.a. Decide whether the assignment to y changes x.
99 99
99 1
1 99
The code is invalid; you can't assign one struct to another.