C Structs — Structure Definition, Nesting, Typedef, and Padding
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
What is a struct in C? A user-defined type that groups related variables of different types under one name.
What is the difference between
.and->operators?.accesses members of a struct variable.->accesses members through a pointer to a struct.What is structure padding? Extra bytes added between members to satisfy alignment requirements. It improves performance but wastes memory.
Why would you use typedef with a struct? To avoid typing the
structkeyword every time you declare a variable of that type.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
What is Next
Now that you understand structs, proceed to Unions to learn about union types that share memory between different members.