Docs

README

Nested Structures in C

Table of Contents

  1. β€’Introduction
  2. β€’What are Nested Structures?
  3. β€’Declaring Nested Structures
  4. β€’Accessing Nested Members
  5. β€’Initializing Nested Structures
  6. β€’Passing Nested Structures to Functions
  7. β€’Pointers to Nested Structures
  8. β€’Multiple Levels of Nesting
  9. β€’Anonymous Nested Structures
  10. β€’Self-Referential Structures
  11. β€’Memory Layout
  12. β€’Common Use Cases
  13. β€’Best Practices
  14. β€’Summary

Introduction

Nested structures allow you to create complex, hierarchical data types by embedding one structure inside another. This powerful feature enables you to model real-world relationships and organize related data in a logical manner.

Why Use Nested Structures?

Consider representing an employee with an address:

Without Nesting:

struct Employee {
    char name[50];
    int emp_id;

    // Address fields mixed with employee fields
    char street[100];
    char city[50];
    char state[30];
    int zip_code;

    // Birth date fields
    int birth_day;
    int birth_month;
    int birth_year;
};

With Nesting:

struct Address {
    char street[100];
    char city[50];
    char state[30];
    int zip_code;
};

struct Date {
    int day;
    int month;
    int year;
};

struct Employee {
    char name[50];
    int emp_id;
    struct Address address;  // Nested
    struct Date birth_date;  // Nested
};

Benefits of Nesting

BenefitDescription
OrganizationGroups related data logically
ReusabilityUse same structure in multiple places
MaintainabilityChanges to Address affect all users
ClarityClear hierarchical relationships
ModularityBuild complex types from simple ones

What are Nested Structures?

A nested structure is a structure that contains one or more structure members. The inner structure is called the nested or embedded structure, while the outer one is the enclosing or container structure.

Visual Representation

struct Employee
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  name[50]  β”‚  emp_id  β”‚  struct Address         β”‚  struct Date β”‚
β”‚            β”‚          β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚            β”‚          β”‚  β”‚ street β”‚ city β”‚ ... β”‚ β”‚  β”‚dayβ”‚monβ”‚yrβ”‚ β”‚
β”‚            β”‚          β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Key Terminology

  • β€’Outer Structure: The containing structure (Employee)
  • β€’Inner Structure: The contained structure (Address, Date)
  • β€’Member Access: Use multiple dot operators (emp.address.city)

Declaring Nested Structures

Method 1: Separate Structure Definitions

// Define inner structures first
struct Date {
    int day;
    int month;
    int year;
};

struct Address {
    char street[100];
    char city[50];
    char state[30];
    int zip_code;
};

// Define outer structure using inner structures
struct Employee {
    char name[50];
    int emp_id;
    float salary;
    struct Date birth_date;    // Nested structure
    struct Date join_date;     // Can use same type multiple times
    struct Address home_addr;  // Nested structure
    struct Address work_addr;
};

Method 2: Inline Definition

struct Person {
    char name[50];
    int age;

    struct {  // Anonymous inline structure
        int day;
        int month;
        int year;
    } dob;

    struct {
        char street[100];
        char city[50];
    } address;
};

Method 3: Definition Inside Outer Structure

struct Order {
    int order_id;

    struct Item {  // Named inline structure
        char name[50];
        int quantity;
        float price;
    } item;  // Only one variable of this type

    float total;
};

// Note: struct Item is accessible outside too
struct Item another_item;  // Valid

Method 4: Using typedef

typedef struct {
    int day, month, year;
} Date;

typedef struct {
    char street[100];
    char city[50];
} Address;

typedef struct {
    char name[50];
    Date birth_date;
    Address home;
} Person;

Accessing Nested Members

Using Multiple Dot Operators

struct Date {
    int day, month, year;
};

struct Person {
    char name[50];
    struct Date dob;
};

int main() {
    struct Person p;

    // Access nested members with multiple dots
    p.dob.day = 15;
    p.dob.month = 8;
    p.dob.year = 1990;
    strcpy(p.name, "John");

    printf("Name: %s\n", p.name);
    printf("DOB: %d/%d/%d\n", p.dob.day, p.dob.month, p.dob.year);

    return 0;
}

Deeply Nested Access

struct Country {
    char name[50];
};

struct State {
    char name[50];
    struct Country country;
};

struct City {
    char name[50];
    struct State state;
};

struct Address {
    char street[100];
    struct City city;
};

struct Person {
    char name[50];
    struct Address address;
};

// Accessing deeply nested member
struct Person p;
strcpy(p.address.city.state.country.name, "USA");

// Reading
printf("Country: %s\n", p.address.city.state.country.name);

Access Pattern Summary

For: struct Outer { struct Middle { struct Inner { int x; } i; } m; } o;

Access x: o.m.i.x

With pointer to outer:
struct Outer *ptr = &o;
Access x: ptr->m.i.x
Access x: (*ptr).m.i.x

Initializing Nested Structures

Method 1: Nested Initializer Lists

struct Date {
    int day, month, year;
};

struct Person {
    char name[50];
    int age;
    struct Date dob;
};

// Using nested braces
struct Person p1 = {
    "John Doe",
    30,
    {15, 8, 1993}  // Nested initializer for dob
};

Method 2: Designated Initializers (C99)

struct Person p2 = {
    .name = "Jane Smith",
    .age = 25,
    .dob = {
        .day = 20,
        .month = 12,
        .year = 1998
    }
};

// Or more compact
struct Person p3 = {
    .name = "Bob",
    .age = 40,
    .dob = {1, 1, 1983}
};

Method 3: Mixed Initialization

struct Address {
    char street[100];
    char city[50];
    int zip;
};

struct Employee {
    char name[50];
    struct Address home;
    struct Address work;
};

struct Employee emp = {
    .name = "Alice",
    .home = {"123 Main St", "New York", 10001},
    .work = {
        .street = "456 Office Blvd",
        .city = "New York",
        .zip = 10002
    }
};

Method 4: Member-by-Member

struct Person p;
strcpy(p.name, "Charlie");
p.age = 35;
p.dob.day = 5;
p.dob.month = 3;
p.dob.year = 1988;

Partial Initialization

// Uninitialized nested members become 0
struct Person p = {
    .name = "Dave"
    // age = 0, dob = {0, 0, 0}
};

Passing Nested Structures to Functions

Pass by Value

struct Date {
    int day, month, year;
};

struct Person {
    char name[50];
    struct Date dob;
};

// Entire structure is copied
void print_person(struct Person p) {
    printf("Name: %s\n", p.name);
    printf("DOB: %d/%d/%d\n", p.dob.day, p.dob.month, p.dob.year);
}

// Modifications don't affect original
void try_modify(struct Person p) {
    p.dob.year = 2000;  // Only modifies local copy
}

int main() {
    struct Person person = {"John", {15, 8, 1990}};
    print_person(person);
    try_modify(person);
    printf("Year after try_modify: %d\n", person.dob.year);  // Still 1990
    return 0;
}

Pass by Pointer (Efficient)

void modify_person(struct Person *p) {
    p->dob.year = 2000;  // Modifies original
    strcpy(p->name, "Modified");
}

// Using const for read-only access
void print_person_ptr(const struct Person *p) {
    printf("Name: %s\n", p->name);
    printf("DOB: %d/%d/%d\n", p->dob.day, p->dob.month, p->dob.year);
}

int main() {
    struct Person person = {"John", {15, 8, 1990}};
    print_person_ptr(&person);
    modify_person(&person);
    printf("After modify: %s, %d\n", person.name, person.dob.year);
    return 0;
}

Returning Nested Structures

struct Person create_person(const char *name, int d, int m, int y) {
    struct Person p;
    strncpy(p.name, name, sizeof(p.name) - 1);
    p.name[sizeof(p.name) - 1] = '\0';
    p.dob.day = d;
    p.dob.month = m;
    p.dob.year = y;
    return p;
}

int main() {
    struct Person p = create_person("Alice", 20, 5, 1995);
    printf("Created: %s, DOB: %d/%d/%d\n",
           p.name, p.dob.day, p.dob.month, p.dob.year);
    return 0;
}

Pointers to Nested Structures

Pointer to Outer Structure

struct Date {
    int day, month, year;
};

struct Person {
    char name[50];
    struct Date dob;
};

int main() {
    struct Person person = {"John", {15, 8, 1990}};
    struct Person *ptr = &person;

    // Access using arrow and dot
    printf("Name: %s\n", ptr->name);
    printf("Day: %d\n", ptr->dob.day);      // arrow then dot
    printf("Month: %d\n", (*ptr).dob.month); // dereference then dots

    // Modify through pointer
    ptr->dob.year = 1991;

    return 0;
}

Pointer to Inner Structure

struct Person person = {"John", {15, 8, 1990}};

// Pointer to inner structure
struct Date *date_ptr = &person.dob;

printf("Year: %d\n", date_ptr->year);
date_ptr->year = 1992;  // Modifies person.dob.year

Multiple Pointers at Different Levels

struct Address {
    char city[50];
    int zip;
};

struct Person {
    char name[50];
    struct Address addr;
};

int main() {
    struct Person people[2] = {
        {"John", {"NYC", 10001}},
        {"Jane", {"LA", 90001}}
    };

    struct Person *person_ptr = people;     // Pointer to Person array
    struct Address *addr_ptr = &people[0].addr;  // Pointer to Address

    printf("Person 0 city: %s\n", person_ptr->addr.city);
    printf("Same via addr_ptr: %s\n", addr_ptr->city);

    // Move to next person
    person_ptr++;
    printf("Person 1 city: %s\n", person_ptr->addr.city);

    return 0;
}

Multiple Levels of Nesting

Three-Level Nesting Example

// Level 1: Innermost
struct Coordinates {
    double latitude;
    double longitude;
};

// Level 2: Contains Level 1
struct Location {
    char name[50];
    struct Coordinates coords;
};

// Level 3: Contains Level 2
struct Branch {
    int branch_id;
    char manager[50];
    struct Location location;
};

// Level 4: Contains Level 3
struct Company {
    char name[100];
    struct Branch headquarters;
    struct Branch branches[10];
    int num_branches;
};

int main() {
    struct Company company = {
        .name = "Tech Corp",
        .headquarters = {
            .branch_id = 1,
            .manager = "CEO",
            .location = {
                .name = "Main Office",
                .coords = {40.7128, -74.0060}
            }
        },
        .num_branches = 0
    };

    // Access deeply nested data
    printf("Company: %s\n", company.name);
    printf("HQ Manager: %s\n", company.headquarters.manager);
    printf("HQ Location: %s\n", company.headquarters.location.name);
    printf("HQ Coords: (%.4f, %.4f)\n",
           company.headquarters.location.coords.latitude,
           company.headquarters.location.coords.longitude);

    return 0;
}

Practical Example: File System

struct Permission {
    int read;
    int write;
    int execute;
};

struct FileInfo {
    char name[256];
    long size;
    struct Permission perms;
};

struct Directory {
    char name[256];
    struct FileInfo files[100];
    int file_count;
    struct Permission perms;
};

struct Volume {
    char label[50];
    long capacity;
    long used;
    struct Directory root;
};

Anonymous Nested Structures

C11 Anonymous Structure Members

struct Point {
    union {
        struct {
            int x;
            int y;
        };  // Anonymous struct
        int coords[2];
    };  // Anonymous union
};

int main() {
    struct Point p = {.x = 10, .y = 20};

    // Direct access - no intermediate member name
    printf("x = %d, y = %d\n", p.x, p.y);
    printf("coords[0] = %d, coords[1] = %d\n", p.coords[0], p.coords[1]);

    return 0;
}

Anonymous vs Named

// Named inner structure
struct Container1 {
    struct {
        int a;
        int b;
    } inner;  // Named member
};

struct Container1 c1;
c1.inner.a = 10;  // Need 'inner' to access

// Anonymous inner structure
struct Container2 {
    struct {
        int a;
        int b;
    };  // Anonymous member
};

struct Container2 c2;
c2.a = 10;  // Direct access

Use Case: Variant Record

struct Message {
    int type;
    union {
        struct {
            int error_code;
            char error_msg[100];
        };  // For error messages
        struct {
            int data_size;
            char data[1000];
        };  // For data messages
    };
};

struct Message msg;
msg.type = 1;
msg.error_code = 404;
strcpy(msg.error_msg, "Not Found");

Self-Referential Structures

A structure can contain a pointer to itself, enabling linked data structures.

Basic Self-Referential Structure

struct Node {
    int data;
    struct Node *next;  // Pointer to same structure type
};

int main() {
    struct Node n1, n2, n3;

    n1.data = 10;
    n1.next = &n2;

    n2.data = 20;
    n2.next = &n3;

    n3.data = 30;
    n3.next = NULL;  // End of list

    // Traverse
    struct Node *current = &n1;
    while (current != NULL) {
        printf("%d -> ", current->data);
        current = current->next;
    }
    printf("NULL\n");

    return 0;
}

Binary Tree Node

struct TreeNode {
    int value;
    struct TreeNode *left;
    struct TreeNode *right;
};

struct TreeNode root = {
    .value = 10,
    .left = NULL,
    .right = NULL
};

Why Pointers Only?

// This is INVALID:
struct Invalid {
    int data;
    struct Invalid next;  // ERROR! Infinite size
};

// This is VALID:
struct Valid {
    int data;
    struct Valid *next;  // OK - pointer has fixed size
};

The compiler needs to know the size of the structure, but if it contained itself (not a pointer), the size would be infinite.


Memory Layout

Contiguous Storage

Nested structures are stored contiguously in memory:

struct Inner {
    int a;  // 4 bytes
    int b;  // 4 bytes
};

struct Outer {
    char c;         // 1 byte + 3 padding
    struct Inner i; // 8 bytes
    char d;         // 1 byte + 3 padding
};

// Memory layout:
// Offset 0:  c (char, 1 byte)
// Offset 1-3: padding (3 bytes)
// Offset 4:  i.a (int, 4 bytes)
// Offset 8:  i.b (int, 4 bytes)
// Offset 12: d (char, 1 byte)
// Offset 13-15: padding (3 bytes)
// Total: 16 bytes

Visualizing Memory

#include <stdio.h>
#include <stddef.h>  // For offsetof

struct Date {
    int day;
    int month;
    int year;
};

struct Person {
    char name[20];
    int age;
    struct Date dob;
};

int main() {
    printf("Size of Date: %zu\n", sizeof(struct Date));
    printf("Size of Person: %zu\n", sizeof(struct Person));

    printf("\nOffsets in Person:\n");
    printf("  name: %zu\n", offsetof(struct Person, name));
    printf("  age: %zu\n", offsetof(struct Person, age));
    printf("  dob: %zu\n", offsetof(struct Person, dob));
    printf("  dob.day: %zu\n", offsetof(struct Person, dob) + offsetof(struct Date, day));

    return 0;
}

Common Use Cases

1. Employee Management System

struct Date {
    int day, month, year;
};

struct Address {
    char street[100];
    char city[50];
    char state[20];
    int zip;
};

struct Employee {
    int emp_id;
    char name[50];
    char department[30];
    float salary;
    struct Date join_date;
    struct Date birth_date;
    struct Address home_address;
    struct Address office_address;
};

2. Graphics/Game Development

struct Vector2D {
    float x, y;
};

struct Color {
    unsigned char r, g, b, a;
};

struct Transform {
    struct Vector2D position;
    struct Vector2D scale;
    float rotation;
};

struct Sprite {
    char name[50];
    struct Transform transform;
    struct Color tint;
    int layer;
    int visible;
};

3. Network Packet Structure

struct IPHeader {
    unsigned int version : 4;
    unsigned int ihl : 4;
    unsigned char tos;
    unsigned short total_length;
    // ... more fields
};

struct TCPHeader {
    unsigned short src_port;
    unsigned short dst_port;
    unsigned int seq_num;
    // ... more fields
};

struct Packet {
    struct IPHeader ip;
    struct TCPHeader tcp;
    char payload[1500];
    int payload_length;
};

4. Database Record

struct Timestamp {
    int year, month, day;
    int hour, minute, second;
};

struct Record {
    int id;
    char data[500];
    struct Timestamp created_at;
    struct Timestamp modified_at;
    int is_deleted;
};

struct Table {
    char name[50];
    struct Record records[1000];
    int record_count;
    struct Timestamp last_modified;
};

Best Practices

1. Define Inner Structures Separately

// Good: Reusable and clear
struct Date {
    int day, month, year;
};

struct Person {
    char name[50];
    struct Date dob;
};

struct Event {
    char title[100];
    struct Date date;  // Reuse Date
};

2. Use typedef for Cleaner Code

typedef struct {
    int day, month, year;
} Date;

typedef struct {
    char name[50];
    Date dob;  // No 'struct' keyword needed
} Person;

3. Limit Nesting Depth

// Avoid: Too deep nesting
a.b.c.d.e.f.g.h.value = 10;

// Better: Create intermediate pointers or restructure
struct DeepLevel *ptr = &a.b.c.d;
ptr->e.f.g.h.value = 10;

4. Use const for Read-Only Functions

void print_person(const struct Person *p) {
    printf("Name: %s\n", p->name);
    printf("DOB: %d/%d/%d\n", p->dob.day, p->dob.month, p->dob.year);
    // Cannot modify p or its members
}

5. Initialize Completely

// Good: All members initialized
struct Person p = {
    .name = "John",
    .dob = {.day = 1, .month = 1, .year = 2000}
};

// Or use zero initialization
struct Person p2 = {0};

6. Document Complex Structures

/**
 * Represents an employee in the company database.
 * Contains personal info, employment details, and addresses.
 */
struct Employee {
    int emp_id;              /**< Unique employee identifier */
    char name[50];           /**< Full name */

    struct Date join_date;   /**< Employment start date */
    struct Address home;     /**< Home address */
    struct Address work;     /**< Office address */
};

Summary

Key Concepts

ConceptDescription
Nested StructureStructure containing structure member(s)
AccessUse multiple dot operators: outer.inner.member
Pointer AccessUse arrow then dot: ptr->inner.member
AnonymousInner structure without name (C11)
Self-ReferenceStructure with pointer to itself

Access Patterns

struct Outer {
    struct Inner {
        int value;
    } inner;
};

struct Outer obj;
struct Outer *ptr = &obj;

// Direct access
obj.inner.value = 10;

// Through pointer
ptr->inner.value = 10;
(*ptr).inner.value = 10;

Memory

  • β€’Nested structures are stored contiguously
  • β€’Padding applies within and between nested structures
  • β€’Use offsetof() to find member positions

When to Use Nested Structures

  • β€’Modeling real-world relationships (Person β†’ Address)
  • β€’Creating reusable components (Date used in multiple places)
  • β€’Organizing complex data hierarchically
  • β€’Building data structures (linked lists, trees)

Next Topics

  • β€’Array of Structures
  • β€’Pointers to Structures
  • β€’Passing Structures to Functions
  • β€’Unions
  • β€’Bit Fields
README - C Programming Tutorial | DeepML