C Nested Structures
A nested structure is a C structure that contains another structure as one of its members. This lets you build larger data models from smaller, meaningful pieces, such as putting an address inside a customer or coordinates inside a rectangle. Nested structures matter because real programs often handle grouped data with natural subgroups, and C lets you represent that organization directly in memory.
Overview: How Nested Structures Work
A normal struct groups related members into one object. A nested structure takes that idea one level deeper: one member’s type is itself a structure type. For example, an Employee can contain a Name structure and an Address structure. The outer object still behaves like one C object, but parts of its storage are complete inner objects with their own members.
Nested structures are not references by default. If struct Customer contains struct Address address;, then the address is stored inside the customer object itself. The bytes for the inner structure are part of the bytes for the outer structure. This is different from a pointer member such as struct Address *address;, which stores only an address pointing somewhere else.
The compiler calculates offsets for every member. For a nested member access like order.ship_to.zip, it first knows the offset of ship_to within order, then the offset of zip within the Address structure. At runtime this becomes ordinary memory access; there is no hidden object system, inheritance, or dynamic lookup.
Nested structures are useful when the inner structure is a reusable concept. A Date type can appear in invoices, employees, and appointments. A Point type can appear in rectangles, circles, and paths. Reusing a smaller structure makes programs easier to read and reduces duplicated field lists.
You can nest structures in two main ways. The first is to define the inner structure separately and then use it as a member type. This is the clearest approach when the inner type is reused. The second is to define an unnamed structure directly inside another structure. That can be acceptable for a one-off group of fields, but it creates a member type that is harder to reuse elsewhere.
Syntax
struct Address {
char city[30];
int zip;
};
struct Customer {
char name[40];
struct Address address;
};
| Part | Meaning |
|---|---|
struct Address |
The inner structure type. It must be declared before it is used as a non-pointer member. |
struct Customer |
The outer structure type that contains another complete structure object. |
address |
A member of struct Customer whose type is struct Address. |
customer.address.zip |
Dot chaining: access the outer member, then the inner member. |
For an object, use the dot operator repeatedly: customer.address.city. For a pointer to the outer structure, use -> first, then . for the inner object: customer_ptr->address.city. If the nested member is also a pointer, use another arrow: customer_ptr->address_ptr->zip.
Examples
A Customer With a Nested Address
#include <stdio.h>
struct Address {
char city[30];
int zip;
};
struct Customer {
char name[40];
struct Address address;
};
int main(void) {
struct Customer customer = {
"Maya Chen",
{"Denver", 80202}
};
printf("Customer: %s
", customer.name);
printf("City: %s
", customer.address.city);
printf("ZIP: %d
", customer.address.zip);
return 0;
}
Output:
Customer: Maya Chen
City: Denver
ZIP: 80202
The Customer structure contains a full Address object. The initializer has one value for name and a nested brace group for address. The expression customer.address.city first selects the address member, then selects the city member inside that address.
Using typedef and Designated Initializers
#include <stdio.h>
typedef struct {
int month;
int day;
int year;
} Date;
typedef struct {
char title[40];
Date due;
int points;
} Assignment;
int main(void) {
Assignment lab = {
.title = "Struct Lab",
.due = { .month = 9, .day = 14, .year = 2026 },
.points = 50
};
printf("%s
", lab.title);
printf("Due: %02d/%02d/%04d
", lab.due.month, lab.due.day, lab.due.year);
printf("Points: %d
", lab.points);
return 0;
}
Output:
Struct Lab
Due: 09/14/2026
Points: 50
typedef makes the type names shorter, so the outer structure can use Date due; instead of struct Date due;. Designated initializers make nested initialization clearer because each value is attached to a member name. This is especially helpful when a structure has several integer fields that could easily be mixed up.
An Array of Nested Structures
#include <stdio.h>
typedef struct {
int x;
int y;
} Point;
typedef struct {
Point top_left;
Point bottom_right;
} Rectangle;
int area(Rectangle r) {
int width = r.bottom_right.x - r.top_left.x;
int height = r.bottom_right.y - r.top_left.y;
return width * height;
}
int main(void) {
Rectangle boxes[] = {
{ {0, 0}, {4, 3} },
{ {2, 1}, {7, 5} },
{ {1, 2}, {6, 8} }
};
int count = (int)(sizeof boxes / sizeof boxes[0]);
for (int i = 0; i < count; i++) {
printf("Box %d area: %d
", i + 1, area(boxes[i]));
}
return 0;
}
Output:
Box 1 area: 12
Box 2 area: 20
Box 3 area: 30
Each Rectangle stores two Point values. The array initializer therefore uses nested braces: one pair for each rectangle and one pair for each point. The area function receives a rectangle by value, so it works with a copy of the complete structure, including both nested points.
Updating Nested Members Through a Pointer
#include <stdio.h>
typedef struct {
int hours;
int minutes;
} Time;
typedef struct {
char name[30];
Time start;
Time end;
} Meeting;
void delay(Meeting *meeting, int minutes) {
meeting->start.minutes += minutes;
meeting->end.minutes += minutes;
meeting->start.hours += meeting->start.minutes / 60;
meeting->start.minutes %= 60;
meeting->end.hours += meeting->end.minutes / 60;
meeting->end.minutes %= 60;
}
int main(void) {
Meeting review = {"Code Review", {10, 45}, {11, 15}};
delay(&review, 20);
printf("%s
", review.name);
printf("Start: %02d:%02d
", review.start.hours, review.start.minutes);
printf("End: %02d:%02d
", review.end.hours, review.end.minutes);
return 0;
}
Output:
Code Review
Start: 11:05
End: 11:35
The function receives a pointer to the outer Meeting. The arrow operator reaches members of the pointed-to meeting, and the dot operator reaches members inside the nested Time objects. Passing a pointer lets the function modify the original meeting instead of a copy.
How It Works Step by Step
When the compiler reads the inner structure definition, it records its member layout and size. When it later sees that type inside another structure, it reserves enough space inside the outer structure to hold the entire inner object. The outer structure may also contain padding before or after the nested member so each member satisfies alignment rules.
Suppose Customer contains char name[40] followed by Address address. A Customer variable has storage for the name array and then storage for the address object. The address object itself has storage for its own members. There is no separate allocation unless you explicitly use a pointer and allocate memory yourself.
Member access is resolved from left to right. In customer.address.zip, customer names the outer object. .address selects the inner object stored inside it. .zip selects a field inside that inner object. With ptr->address.zip, ptr->address is equivalent to (*ptr).address, and then .zip accesses the nested member.
Structure assignment copies nested structures too. If you write second = first;, every stored byte that belongs to the structure value is copied, including arrays and nested structure members. But if one of those members is a pointer, the pointer value is copied, not the separate memory it points to. Nested structures avoid that sharing problem when the data should live directly inside the outer object.
Common Mistakes
Forgetting to Declare the Inner Type First
A structure cannot contain a complete object of a type the compiler does not know yet. Write the inner type first, then the outer type. A pointer to an incomplete structure can be declared earlier, but a real nested member needs the complete size.
Using the Wrong Operator Chain
If meeting is a structure object, write meeting.start.hours. If meeting_ptr is a pointer to a structure, write meeting_ptr->start.hours. Writing meeting_ptr.start.hours is wrong because the pointer itself has no start member.
Assuming Nested Initialization Is Flat
Flat initializer lists can compile, but they become hard to read and easy to break when fields are reordered. Prefer braces or designated initializers for nested data, such as .due = { .month = 9, .day = 14, .year = 2026 }.
Confusing Embedded Structures With Pointers
struct Address address; stores the full address inside the outer object. struct Address *address; stores only a pointer. The pointer form can represent optional or shared data, but it also requires careful lifetime management so the pointer does not become invalid.
Best Practices
- Define reusable inner structures separately, such as
Date,Point, orAddress. - Use designated initializers for nested structures when field order is not obvious.
- Use embedded nested structures when the inner data is owned by the outer object and always exists.
- Use pointer members only when the inner object is optional, shared, very large, or managed elsewhere.
- Pass large outer structures by pointer, and use
constwhen a function should only inspect them. - Keep nesting meaningful. Deep chains like
a.b.c.d.ecan signal that the data model should be simplified. - Do not depend on guessed byte offsets or padding. Access nested data by member name.
Practice Exercises
- Create a
Datestructure and aPersonstructure that stores a name and birthday. Print the person’s birthday inYYYY-MM-DDformat. - Define
PointandCirclestructures. Store a center point and radius in the circle, then print the center coordinates and diameter. - Write a function that accepts a pointer to an
Orderstructure containing a nested shipping address, then updates only the ZIP code.
Summary
- A nested structure is a structure member whose type is another structure.
- Embedded nested structures are stored directly inside the outer structure object.
- Use chained member access such as
customer.address.cityorcustomer_ptr->address.city. - Nested initialization is clearest with braces or designated initializers.
- Structure assignment copies nested structure values, but pointer members still copy only addresses.
- Nested structures help C programs model real data while preserving predictable memory layout.
