CoreTechED

Loading

C Structures: Complete Guide with Examples

Learn C structures: definition, initialization, access, nested structures, pointers, typedef, and bit fields. Master grouping data in C programming.

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

AspectArrayStructure
Data typesSame type onlyDifferent types allowed
SizeFixed (homogeneous)Sum of members + padding
AccessIndex-basedMember name-based
Use caseCollections of same-type dataGrouping 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

  1. Nested structure: Address is nested inside Employee.
  2. Pointer to structure: displayEmployee takes const Employee *.
  3. Pass by pointer: giveRaise modifies original employee.
  4. Pass by value: findHighestPaid returns a structure.
  5. Array of structures: Employee employees[MAX_EMPLOYEES].
  6. Structure initialization: initializeEmployee sets 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 struct keyword 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 typedef to 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

  1. Use typedef for cleaner structure declarations.
  2. Pass structures by pointer to avoid copying overhead.
  3. Use const for read-only structure parameters.
  4. Initialize structures to avoid garbage values.
  5. Implement deep copy when structures contain pointers.
  6. Use sizeof to determine structure size.
  7. Avoid bit fields for portable code.
  8. Document structure members with comments.
  9. Use designated initializers for clarity (C99).
  10. Free dynamically allocated memory in structures to prevent leaks.

By mastering structures, you’ll write organized, maintainable C programs that effectively model complex data.

Leave a Reply

Your email address will not be published. Required fields are marked *