C Structures: Complete Guide with Examples
C Structures: A Comprehensive Tutorial
A structure in C is a user-defined data type that groups related variables of different data types under a single name. It is useful for representing real-world entities and organizing related data into a single unit. Structures are fundamental to building complex data structures like linked lists, trees, and graphs.
This tutorial provides an in-depth exploration of C structures: definition, initialization, access, copying, passing to functions, typedef, size, nested structures, pointers, self-referential structures, bit fields, common pitfalls, and best practices.
Conceptual Introduction & Fundamentals
What is a Structure?
A structure is a collection of variables (called members) of potentially different data types grouped under a single name. It allows you to treat related data as a single unit.
Example:
c
struct Student { char name[50]; int age; float grade; }; This defines a Student structure with three members: name, age, and grade.
Why Use Structures?
- Group related data: Represent real-world entities (students, employees, products).
- Organize code: Keep related information together.
- Build complex data structures: Linked lists, trees, graphs.
- Pass multiple values: Return multiple related values from functions.
- Improve readability: Clearer than using separate variables.
Structure vs. Array
| Aspect | Array | Structure |
|---|---|---|
| Data types | Same type only | Different types allowed |
| Size | Fixed (homogeneous) | Sum of members + padding |
| Access | Index-based | Member name-based |
| Use case | Collections of same-type data | Grouping related different-type data |
Syntax & Core Mechanics
Defining a Structure
Syntax:
c
struct structure_name { data_type member1; data_type member2; // ... }; Example:
c
struct Point { int x; int y; }; Declaring Structure Variables
c
#include <stdio.h> struct Point { int x; int y; }; int main(void) { struct Point p1; // Declaration p1.x = 10; p1.y = 20; printf("p1: (%d, %d)\n", p1.x, p1.y); return 0; } Expected output:
text
p1: (10, 20)
Accessing Structure Members
Use the dot operator (.) for structure variables, and the arrow operator (->) for structure pointers.
c
#include <stdio.h> struct Student { int age; float marks; }; int main(void) { struct Student s = {20, 85.5f}; /* Dot operator */ printf("Age: %d\n", s.age); printf("Marks: %.1f\n", s.marks); /* Pointer with arrow operator */ struct Student *ptr = &s; printf("Age via pointer: %d\n", ptr->age); printf("Marks via pointer: %.1f\n", ptr->marks); return 0; } Expected output:
text
Age: 20 Marks: 85.5 Age via pointer: 20 Marks via pointer: 85.5
Initialization
Method 1: Initializer List (Sequential)
c
struct Student s1 = {"Rahul", 20, 18.5f}; Method 2: Designated Initializers (C99)
c
struct Student s2 = {.age = 18, .name = "Vikas", .grade = 22.0f}; Method 3: Zero Initialization
c
struct Student s3 = {0}; // All members set to 0/NULL Method 4: Assignment After Declaration
c
struct Student s4; s4.age = 25; s4.marks = 90.5f;
Complete example:
c
#include <stdio.h> struct Student { char name[50]; int age; float grade; }; int main(void) { struct Student s1 = {"Rahul", 20, 18.5f}; struct Student s2 = {.age = 18, .name = "Vikas", .grade = 22.0f}; struct Student s3 = {0}; printf("%s\t%d\t%.2f\n", s1.name, s1.age, s1.grade); printf("%s\t%d\t%.2f\n", s2.name, s2.age, s2.grade); printf("s3: %s\t%d\t%.2f\n", s3.name, s3.age, s3.grade); return 0; } Expected output:
text
Rahul 20 18.50 Vikas 18 22.00 s3: 0 0.00
Copying Structures
Structures can be copied using the assignment operator.
c
#include <stdio.h> struct Student { int id; char grade; }; int main(void) { struct Student s1 = {1, 'A'}; struct Student s2 = s1; // Copy printf("s2.id = %d\n", s2.id); printf("s2.grade = %c\n", s2.grade); return 0; } Expected output:
text
s2.id = 1 s2.grade = A
Note: This is a shallow copy. Pointers within the structure are copied but the pointed-to data is not duplicated.
Passing Structures to Functions
Structures can be passed by value or by pointer.
c
#include <stdio.h> struct Point { int x; int y; }; void incrementByValue(struct Point p) { p.x++; p.y++; } void incrementByPointer(struct Point *p) { p->x++; p->y++; } int main(void) { struct Point p1 = {10, 20}; struct Point p2 = {10, 20}; incrementByValue(p1); incrementByPointer(&p2); printf("p1: (%d, %d)\n", p1.x, p1.y); // Unchanged printf("p2: (%d, %d)\n", p2.x, p2.y); // Modified return 0; } Expected output:
text
p1: (10, 20) p2: (11, 21)
typedef with Structures
typedef creates an alias for the structure type.
c
#include <stdio.h> typedef struct { int x; int y; } Point; int main(void) { Point p = {10, 20}; printf("p: (%d, %d)\n", p.x, p.y); return 0; } Expected output:
text
p: (10, 20)
Size of a Structure
The size may be larger than the sum of member sizes due to padding.
c
#include <stdio.h> struct Example { char c; // 1 byte int i; // 4 bytes char d; // 1 byte }; int main(void) { printf("sizeof(char) = %zu\n", sizeof(char)); printf("sizeof(int) = %zu\n", sizeof(int)); printf("sizeof(struct Example) = %zu\n", sizeof(struct Example)); printf("Sum of members = %zu\n", sizeof(char) + sizeof(int) + sizeof(char)); return 0; } Expected output (typical):
text
sizeof(char) = 1 sizeof(int) = 4 sizeof(struct Example) = 12 Sum of members = 6
The extra 6 bytes are padding for alignment.
Nested Structures
A structure can contain another structure as a member.
c
#include <stdio.h> struct Address { char city[50]; int zip; }; struct Person { char name[50]; int age; struct Address address; }; int main(void) { struct Person p = {"Alice", 30, {"New York", 10001}}; printf("Name: %s\n", p.name); printf("Age: %d\n", p.age); printf("City: %s\n", p.address.city); printf("ZIP: %d\n", p.address.zip); return 0; } Expected output:
text
Name: Alice Age: 30 City: New York ZIP: 10001
Self-Referential Structures
Structures that contain pointers to the same structure type.
c
struct Node { int data; struct Node *next; }; Used for linked lists, trees, and graphs.
Bit Fields
Specify the size of members in bits.
c
#include <stdio.h> struct Status { unsigned int isReady : 1; unsigned int error : 1; unsigned int mode : 2; }; int main(void) { struct Status s = {0}; s.isReady = 1; s.error = 0; s.mode = 2; printf("isReady: %u\n", s.isReady); printf("error: %u\n", s.error); printf("mode: %u\n", s.mode); printf("Size of struct: %zu bytes\n", sizeof(s)); return 0; } Expected output (typical):
text
isReady: 1 error: 0 mode: 2 Size of struct: 4 bytes
Full Practical Code Example with Explanation
This program demonstrates structures in a realistic scenario: an employee management system.
Program: Employee Management System
c
/** * employee_management.c * Demonstrates C structures with employee records. * Includes initialization, nested structures, pointers, and function passing. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #define MAX_EMPLOYEES 100 #define MAX_NAME_LEN 50 #define MAX_DEPT_LEN 50 #define MAX_LINE_LEN 100 /* Address structure (nested) */ typedef struct { char street[100]; char city[50]; int zip; } Address; /* Employee structure */ typedef struct { int id; char name[MAX_NAME_LEN]; int age; double salary; Address address; } Employee; /* Function prototypes */ void initializeEmployee(Employee *emp, int id, const char *name, int age, double salary, const char *street, const char *city, int zip); void displayEmployee(const Employee *emp); void displayAllEmployees(const Employee employees[], int count); void giveRaise(Employee *emp, double percentage); double calculateAverageSalary(const Employee employees[], int count); Employee findHighestPaid(const Employee employees[], int count); int readInteger(const char *prompt, int *outValue); int main(void) { Employee employees[MAX_EMPLOYEES]; int employeeCount = 0; int choice; char buffer[MAX_LINE_LEN]; /* Initialize some employees */ initializeEmployee(&employees[0], 1001, "Alice Johnson", 28, 55000.0, "123 Main St", "New York", 10001); initializeEmployee(&employees[1], 1002, "Bob Smith", 35, 72000.0, "456 Oak Ave", "Los Angeles", 90001); initializeEmployee(&employees[2], 1003, "Charlie Brown", 42, 68000.0, "789 Pine Rd", "Chicago", 60601); employeeCount = 3; printf("=== Employee Management System ===\n"); while (1) { printf("\n--- Menu ---\n"); printf("1. Display All Employees\n"); printf("2. Add Employee\n"); printf("3. Give Raise\n"); printf("4. Calculate Average Salary\n"); printf("5. Find Highest Paid Employee\n"); printf("6. Exit\n"); printf("Enter choice: "); fflush(stdout); if (fgets(buffer, sizeof(buffer), stdin) == NULL) { fprintf(stderr, "Error reading choice.\n"); return EXIT_FAILURE; } choice = atoi(buffer); switch (choice) { case 1: displayAllEmployees(employees, employeeCount); break; case 2: { if (employeeCount >= MAX_EMPLOYEES) { printf("Maximum employees reached.\n"); break; } Employee newEmp; char name[MAX_NAME_LEN]; char street[100]; char city[50]; int age, zip; double salary; int id = 1000 + employeeCount + 1; printf("Enter name: "); fflush(stdout); if (fgets(name, sizeof(name), stdin) == NULL) break; name[strcspn(name, "\n")] = '\0'; if (readInteger("Enter age: ", &age) != 0) break; printf("Enter salary: $"); fflush(stdout); if (fgets(buffer, sizeof(buffer), stdin) == NULL) break; salary = strtod(buffer, NULL); printf("Enter street: "); fflush(stdout); if (fgets(street, sizeof(street), stdin) == NULL) break; street[strcspn(street, "\n")] = '\0'; printf("Enter city: "); fflush(stdout); if (fgets(city, sizeof(city), stdin) == NULL) break; city[strcspn(city, "\n")] = '\0'; if (readInteger("Enter ZIP: ", &zip) != 0) break; initializeEmployee(&employees[employeeCount], id, name, age, salary, street, city, zip); employeeCount++; printf("Employee added with ID %d.\n", id); break; } case 3: { int empId; double percentage; if (readInteger("Enter employee ID: ", &empId) != 0) break; int found = 0; for (int i = 0; i < employeeCount; i++) { if (employees[i].id == empId) { printf("Enter raise percentage: "); fflush(stdout); if (fgets(buffer, sizeof(buffer), stdin) != NULL) { percentage = strtod(buffer, NULL); giveRaise(&employees[i], percentage); printf("Raise applied to %s.\n", employees[i].name); } found = 1; break; } } if (!found) { printf("Employee with ID %d not found.\n", empId); } break; } case 4: { double avg = calculateAverageSalary(employees, employeeCount); printf("Average salary: $%.2f\n", avg); break; } case 5: { Employee highest = findHighestPaid(employees, employeeCount); printf("Highest paid employee:\n"); displayEmployee(&highest); break; } case 6: printf("Exiting.\n"); return EXIT_SUCCESS; default: printf("Invalid choice. Please try again.\n"); break; } } return EXIT_SUCCESS; } /* Initializes an employee */ void initializeEmployee(Employee *emp, int id, const char *name, int age, double salary, const char *street, const char *city, int zip) { emp->id = id; strncpy(emp->name, name, MAX_NAME_LEN - 1); emp->name[MAX_NAME_LEN - 1] = '\0'; emp->age = age; emp->salary = salary; strncpy(emp->address.street, street, 99); emp->address.street[99] = '\0'; strncpy(emp->address.city, city, 49); emp->address.city[49] = '\0'; emp->address.zip = zip; } /* Displays a single employee */ void displayEmployee(const Employee *emp) { printf("ID: %d\n", emp->id); printf("Name: %s\n", emp->name); printf("Age: %d\n", emp->age); printf("Salary: $%.2f\n", emp->salary); printf("Address: %s, %s, %d\n", emp->address.street, emp->address.city, emp->address.zip); } /* Displays all employees */ void displayAllEmployees(const Employee employees[], int count) { if (count == 0) { printf("No employees to display.\n"); return; } printf("\n=== Employee List ===\n"); for (int i = 0; i < count; i++) { displayEmployee(&employees[i]); printf("---\n"); } } /* Gives a raise to an employee */ void giveRaise(Employee *emp, double percentage) { if (percentage > 0) { emp->salary += emp->salary * (percentage / 100.0); } } /* Calculates average salary */ double calculateAverageSalary(const Employee employees[], int count) { if (count == 0) return 0.0; double sum = 0.0; for (int i = 0; i < count; i++) { sum += employees[i].salary; } return sum / count; } /* Finds highest paid employee */ Employee findHighestPaid(const Employee employees[], int count) { Employee highest = employees[0]; for (int i = 1; i < count; i++) { if (employees[i].salary > highest.salary) { highest = employees[i]; } } return highest; } /* Reads an integer safely */ int readInteger(const char *prompt, int *outValue) { char buffer[MAX_LINE_LEN]; char *endPtr; long val; printf("%s", prompt); fflush(stdout); if (fgets(buffer, sizeof(buffer), stdin) == NULL) { return -1; } errno = 0; val = strtol(buffer, &endPtr, 10); if (endPtr == buffer || (*endPtr != '\n' && *endPtr != '\0')) { return -1; } if (errno == ERANGE || val < -2147483647 - 1 || val > 2147483647) { return -1; } *outValue = (int)val; return 0; } Explanation of Structure Usage
- Nested structure:
Addressis nested insideEmployee. - Pointer to structure:
displayEmployeetakesconst Employee *. - Pass by pointer:
giveRaisemodifies original employee. - Pass by value:
findHighestPaidreturns a structure. - Array of structures:
Employee employees[MAX_EMPLOYEES]. - Structure initialization:
initializeEmployeesets all members.
Expected Output (sample run)
text
=== Employee Management System === --- Menu --- 1. Display All Employees 2. Add Employee 3. Give Raise 4. Calculate Average Salary 5. Find Highest Paid Employee 6. Exit Enter choice: 1 === Employee List === ID: 1001 Name: Alice Johnson Age: 28 Salary: $55000.00 Address: 123 Main St, New York, 10001 --- ID: 1002 Name: Bob Smith Age: 35 Salary: $72000.00 Address: 456 Oak Ave, Los Angeles, 90001 --- ID: 1003 Name: Charlie Brown Age: 42 Salary: $68000.00 Address: 789 Pine Rd, Chicago, 60601 --- --- Menu --- ... Enter choice: 4 Average salary: $65000.00 --- Menu --- ... Enter choice: 5 Highest paid employee: ID: 1002 Name: Bob Smith Age: 35 Salary: $72000.00 Address: 456 Oak Ave, Los Angeles, 90001
Common Pitfalls, Edge Cases & Memory Considerations
1. Forgetting Semicolon After Structure Definition
The structure definition must end with a semicolon.
c
struct Point { int x; int y; } // BUG: missing semicolon Solution: Add semicolon: };
2. Shallow Copy with Pointers
Copying a structure with pointers copies the pointer, not the data.
c
struct Data { int *ptr; }; struct Data d1; d1.ptr = malloc(sizeof(int)); struct Data d2 = d1; // Shallow copy: d2.ptr == d1.ptr Solution: Implement deep copy manually if needed.
3. Passing Large Structures by Value
Passing large structures by value copies all data, consuming stack space.
c
void func(struct LargeStruct s) { // Copies entire structure } Solution: Pass by pointer: void func(const struct LargeStruct *s).
4. Uninitialized Structure Members
Uninitialized automatic structures contain garbage.
c
struct Point p; printf("%d\n", p.x); // Garbage value Solution: Initialize: struct Point p = {0};
5. Structure Padding Confusion
Assuming structure size equals sum of member sizes.
c
struct Example { char c; int i; }; /* sizeof may be 8, not 5, due to padding */ Solution: Use sizeof to get actual size.
6. String Assignment to Character Array
Cannot assign strings to character arrays after declaration.
c
struct Student s; s.name = "Alice"; // BUG: cannot assign to array
Solution: Use strcpy(s.name, "Alice");
7. Self-Referential Structure with Value (Not Pointer)
A structure cannot contain itself by value (infinite size).
c
struct Node { int data; struct Node next; // BUG: infinite size }; Solution: Use a pointer: struct Node *next;
8. Using == on Structures
Cannot compare structures with ==.
c
struct Point p1 = {1, 2}; struct Point p2 = {1, 2}; if (p1 == p2) { // BUG: cannot compare structures } Solution: Compare members individually or use memcmp.
9. Bit Field Portability
Bit field layout is implementation-defined and may vary across compilers.
Solution: Avoid bit fields for portable code; use bitwise operations on integers instead.
10. Memory Considerations
- Structures may contain padding for alignment.
- Large structures passed by value consume stack space.
- Dynamic structures (with pointers) require careful memory management.
- Arrays of structures consume
count × sizeof(struct)bytes.
Summary & Best Practices
Structures are a fundamental feature of C that enable grouping of related data into a single unit. They form the foundation for building complex data structures and modeling real-world entities.
Key Takeaways
- Use
structkeyword to define structures. - Access members with dot operator (
.) or arrow operator (->). - Initialize with initializer lists or designated initializers.
- Copy structures using assignment operator (shallow copy).
- Pass structures by pointer for efficiency.
- Use
typedefto simplify structure declarations. - Size includes padding for alignment.
- Nested structures group related structures.
- Self-referential structures enable linked data structures.
- Bit fields save memory for small values.
Best Practices
- Use
typedeffor cleaner structure declarations. - Pass structures by pointer to avoid copying overhead.
- Use
constfor read-only structure parameters. - Initialize structures to avoid garbage values.
- Implement deep copy when structures contain pointers.
- Use
sizeofto determine structure size. - Avoid bit fields for portable code.
- Document structure members with comments.
- Use designated initializers for clarity (C99).
- Free dynamically allocated memory in structures to prevent leaks.
By mastering structures, you’ll write organized, maintainable C programs that effectively model complex data.



