Skip to content

C Structs — Structure Definition, Nesting, Typedef, and Padding

DodaTech Updated 2026-06-28 8 min read

In this tutorial, you will learn about C Structs. We cover key concepts, practical examples, and best practices to help you master this topic.

C structs are user-defined data types that group related variables of different types under a single name, enabling data aggregation and custom Composite types.

Why It Matters

Structs are the foundation of data organization in C. Without structs, you would need separate variables for every attribute of an entity. Structs enable you to model real-world entities, create self-contained data packages, and build complex data structures. They are essential for system programming, database records, network protocols, and hardware interfaces.

Real-World Use

The POSIX stat struct holds file metadata. Network packet headers are defined as structs. Device driver registers are mapped to struct fields. The FILE type used for file I/O is a struct. Durga Antivirus Pro uses structs for virus signatures, scan results, and configuration parameters.

What You Will Learn

  • Defining and using struct types
  • Accessing struct members with dot and arrow operators
  • Nested structs and complex hierarchies
  • Using typedef to simplify struct syntax
  • Structure padding and alignment

Learning Path

flowchart LR
  A[Memory Layout] --> B[Structs
You are here] B --> C[Unions] C --> D[Bit Fields] D --> E[File I/O] style B fill:#f90,color:#fff

Defining and Using Structs

#include <stdio.h>
#include <string.h>

// Define a struct type
struct Student {
    char name[50];
    int age;
    float gpa;
};

int main() {
    // Declare and initialize a struct variable
    struct Student s1;
    
    // Assign values to members
    strcpy(s1.name, "Alice");
    s1.age = 20;
    s1.gpa = 3.8;
    
    printf("Name: %s\n", s1.name);
    printf("Age: %d\n", s1.age);
    printf("GPA: %.1f\n", s1.gpa);
    
    // Initialize at declaration
    struct Student s2 = {"Bob", 22, 3.5};
    
    // Designated initializers (C99+)
    struct Student s3 = {.name = "Charlie", .age = 21, .gpa = 3.9};
    
    printf("\nStudent 2: %s, GPA: %.1f\n", s2.name, s2.gpa);
    printf("Student 3: %s, GPA: %.1f\n", s3.name, s3.gpa);
    
    return 0;
}

Expected output:

Name: Alice
Age: 20
GPA: 3.8

Student 2: Bob, GPA: 3.5
Student 3: Charlie, GPA: 3.9

The Arrow Operator (->)

When you have a pointer to a struct, use -> to access members:

#include <stdio.h>
#include <string.h>

struct Point {
    int x;
    int y;
};

int main() {
    struct Point p1 = {10, 20};
    struct Point *ptr = &p1;
    
    // Dot operator (direct access)
    printf("Direct: x=%d, y=%d\n", p1.x, p1.y);
    
    // Arrow operator (pointer access)
    printf("Through pointer: x=%d, y=%d\n", ptr->x, ptr->y);
    
    // Equivalent but verbose pointer notation
    printf("Dereference: x=%d, y=%d\n", (*ptr).x, (*ptr).y);
    
    // Modify through pointer
    ptr->x = 100;
    ptr->y = 200;
    printf("After modification: x=%d, y=%d\n", p1.x, p1.y);
    
    return 0;
}

Expected output:

Direct: x=10, y=20
Through pointer: x=10, y=20
Dereference: x=10, y=20
After modification: x=100, y=200

ptr->member is syntactic sugar for (*ptr).member. The arrow operator makes code cleaner when working with pointer-to-struct.

Nested Structs

Structs can contain other structs as members:

#include <stdio.h>
#include <string.h>

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

struct Employee {
    char name[50];
    int id;
    struct Address address;  // Nested struct
};

int main() {
    struct Employee emp;
    
    strcpy(emp.name, "Alice Smith");
    emp.id = 1001;
    strcpy(emp.address.street, "123 Main St");
    strcpy(emp.address.city, "Portland");
    emp.address.zip_code = 97201;
    
    printf("Employee: %s (ID: %d)\n", emp.name, emp.id);
    printf("Address: %s, %s %d\n",
           emp.address.street,
           emp.address.city,
           emp.address.zip_code);
    
    // Initialize nested struct
    struct Employee emp2 = {
        "Bob Jones",
        1002,
        {"456 Oak Ave", "Seattle", 98101}
    };
    
    printf("\nEmployee 2: %s lives in %s\n",
           emp2.name, emp2.address.city);
    
    return 0;
}

Expected output:

Employee: Alice Smith (ID: 1001)
Address: 123 Main St, Portland 97201

Employee 2: Bob Jones lives in Seattle

Typedef with Structs

typedef creates an alias for the struct type, eliminating the need for the struct keyword:

#include <stdio.h>

// Without typedef
struct Point {
    int x;
    int y;
};

// With typedef
typedef struct {
    int x;
    int y;
} Point2D;

// Typedef with struct name
typedef struct Rectangle {
    int width;
    int height;
} Rectangle;

int main() {
    struct Point p1 = {1, 2};   // Requires 'struct' keyword
    Point2D p2 = {3, 4};         // No 'struct' needed
    Rectangle r1 = {100, 200};   // No 'struct' needed
    
    printf("Point: (%d, %d)\n", p1.x, p1.y);
    printf("Point2D: (%d, %d)\n", p2.x, p2.y);
    printf("Rectangle: %d x %d\n", r1.width, r1.height);
    
    return 0;
}

Structure Padding

The compiler may add padding bytes between struct members to satisfy alignment requirements:

#include <stdio.h>

struct Packed {
    char a;   // 1 byte
    // 3 bytes padding (to align int)
    int b;    // 4 bytes
    char c;   // 1 byte
    // 3 bytes padding (to align struct size to 4)
};

struct Optimized {
    int b;    // 4 bytes
    char a;   // 1 byte
    char c;   // 1 byte
    // 2 bytes padding
};

int main() {
    printf("sizeof(struct Packed): %zu\n", sizeof(struct Packed));
    printf("sizeof(struct Optimized): %zu\n", sizeof(struct Optimized));
    
    printf("Offsets:\n");
    printf("  Packed.a: %zu\n", offsetof(struct Packed, a));
    printf("  Packed.b: %zu\n", offsetof(struct Packed, b));
    printf("  Packed.c: %zu\n", offsetof(struct Packed, c));
    
    return 0;
}

Expected output:

sizeof(struct Packed): 12
sizeof(struct Optimized): 8
Offsets:
  Packed.a: 0
  Packed.b: 4
  Packed.c: 8

Why Padding Exists

Processors access memory most efficiently when data is aligned to its natural boundary. A 4-byte int should be at an address divisible by 4. The compiler adds padding to satisfy these requirements, trading memory for performance.

To minimize padding, order members from largest to smallest.

Passing Structs to Functions

#include <stdio.h>
#include <string.h>

typedef struct {
    char name[50];
    int score;
} Score;

// Pass by value (copy)
void print_score(Score s) {
    printf("%s scored %d\n", s.name, s.score);
}

// Pass by pointer (modify original)
void add_bonus(Score *s, int bonus) {
    s->score += bonus;
}

// Return a struct
Score create_score(const char *name, int score) {
    Score s;
    strncpy(s.name, name, sizeof(s.name) - 1);
    s.name[sizeof(s.name) - 1] = '\0';
    s.score = score;
    return s;
}

int main() {
    Score s1 = create_score("Alice", 85);
    print_score(s1);
    
    add_bonus(&s1, 10);
    print_score(s1);  // 95
    
    return 0;
}

Expected output:

Alice scored 85
Alice scored 95

Pass by Value vs Pointer

  • Pass by value: copies the entire struct. Safe (no side effects) but slow for large structs.
  • Pass by pointer: passes only the address. Fast for large structs but the function can modify the original.

For structs larger than a few bytes, prefer passing by pointer. Use const to prevent modification:

void display(const struct LargeStruct *s) {
    // Read-only access
    printf("Name: %s\n", s->name);
}

Common Mistakes

1. Forgetting the Semicolon After Struct Definition

struct Point {
    int x;
    int y;
}  // Missing semicolon -- compiler error!

Always end a struct definition with a semicolon.

2. Copying Structs with Pointers to Dynamic Memory

typedef struct { char *name; } Person;
Person p1 = {malloc(10)};
Person p2 = p1;  // Both point to same memory!
free(p1.name);
// p2.name is now a dangling pointer

Perform a deep copy: allocate new memory and copy the content.

3. Assuming No Padding

struct { char a; int b; } s;
// sizeof(s) is likely 8, not 5

Always use sizeof() instead of summing member sizes.

4. Returning Pointer to Local Struct

struct Point *bad() {
    struct Point p = {1, 2};
    return &p;  // p is destroyed!
}

Return the struct by value or use malloc.

5. Not Using Typedef

void process(struct Point p) { }  // Verbose

Using typedef makes code cleaner, especially with function parameters.

Practice Questions

  1. What is a struct in C? A user-defined type that groups related variables of different types under one name.

  2. What is the difference between . and -> operators? . accesses members of a struct variable. -> accesses members through a pointer to a struct.

  3. What is structure padding? Extra bytes added between members to satisfy alignment requirements. It improves performance but wastes memory.

  4. Why would you use typedef with a struct? To avoid typing the struct keyword every time you declare a variable of that type.

  5. Challenge: Define a struct representing a book (title, author, year, pages) and write functions to create, display, and compare books.

Mini Project: Library Catalog

#include <stdio.h>
#include <string.h>

typedef struct {
    char title[100];
    char author[50];
    int year;
    int pages;
} Book;

Book create_book(const char *title, const char *author, int year, int pages) {
    Book b;
    strncpy(b.title, title, sizeof(b.title) - 1);
    strncpy(b.author, author, sizeof(b.author) - 1);
    b.year = year;
    b.pages = pages;
    return b;
}

void display_book(const Book *b) {
    printf("'%s' by %s (%d) - %d pages\n",
           b->title, b->author, b->year, b->pages);
}

int main() {
    Book library[3];
    
    library[0] = create_book("The C Programming Language",
                             "Kernighan & Ritchie", 1978, 272);
    library[1] = create_book("1984", "George Orwell", 1949, 328);
    library[2] = create_book("Clean Code", "Robert C. Martin", 2008, 464);
    
    printf("Library Catalog:\n");
    printf("=================\n");
    for (int i = 0; i < 3; i++) {
        display_book(&library[i]);
    }
    
    return 0;
}

FAQ

Can a struct contain a pointer to itself?

Yes. This is how linked lists and trees are defined. The struct contains a pointer to another instance of the same struct type.

What is the size of an empty struct?

In C, an empty struct has size 0. In C++, it has size 1 to ensure distinct addresses for different objects.

Can I compare two structs with ==?

No. The == operator does not work with structs. You must compare each member individually or use memcmp().

How do I initialize all struct members to zero?

Use {0}: struct Point p = {0}; sets all members to zero regardless of type.

What is a flexible array member?

In C99+, the last member of a struct can be an incomplete array type: int flex[];. Used for variable-length data at the end of a struct.

What is Next

Now that you understand structs, proceed to Unions to learn about union types that share memory between different members.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro

Home Browse C