C Struct Pointers
Working with structures and pointers together
🏗️ What are C Struct Pointers?
Struct pointers in C allow you to access and manipulate structure data using memory addresses. They enable efficient data handling and dynamic memory allocation for complex data structures.
// Basic struct pointer example
struct Person *ptr;
ptr->name = "John";
ptr->age = 25;
Key Concepts
Structures
Group related data together
struct Student {
char name[50];
int age;
float grade;
};
Pointers
Store memory addresses of variables
struct Student *ptr;
ptr = &student1
Arrow Operator
Access struct members via pointer
ptr->name;
ptr->age;
ptr->grade;
Dynamic Memory
Allocate memory at runtime
ptr = malloc(sizeof(struct Student));
🔹 Basic Struct and Pointer
Understanding the relationship between structures and pointers is fundamental to C programming, enabling efficient data manipulation and memory management. When you create a structure variable like struct Point p = {10, 20}; and a pointer struct Point *ptr = &p;, the pointer stores the memory address of the structure. You can then access members through the pointer using ptr->x which is cleaner than (*ptr).x. This relationship is crucial because it allows functions to work with structures without copying them, enables dynamic memory allocation, and forms the basis for complex data structures. Mastering structure pointers opens the door to advanced programming techniques like linked data structures and efficient memory usage patterns.
#include <stdio.h>
#include <string.h>
struct Person {
char name[30];
int age;
float height;
};
int main() {
// Create a struct variable
struct Person person1;
// Create a pointer to struct
struct Person *ptr = &person1
// Access using pointer with arrow operator
strcpy(ptr->name, "Alice");
ptr->age = 25;
ptr->height = 5.6;
// Display data
printf("Name: %s\n", ptr->name);
printf("Age: %d\n", ptr->age);
printf("Height: %.1f\n", ptr->height);
return 0;
}
Output:
Name: Alice Age: 25 Height: 5.6
🔹 Arrow vs Dot Operator
The arrow operator (->) and dot operator (.) both access structure members, but they're used in different contexts based on whether you have a structure or a pointer. Use the dot operator with structure variables: person.age = 25; accesses the age member of the person structure directly. Use the arrow operator with structure pointers: ptr->age = 25; accesses the member through the pointer. The arrow operator ptr->member is syntactic sugar for (*ptr).member, making pointer dereferencing more readable. Understanding when to use each operator is essential for avoiding compilation errors and writing clear, idiomatic C code. The choice depends on whether your variable is a structure instance or a pointer to a structure.
Access Methods:
- Dot operator (.): Used with struct variables
- Arrow operator (->): Used with struct pointers
- (*ptr).member: Alternative to arrow operator
#include <stdio.h>
struct Book {
char title[40];
int pages;
float price;
};
int main() {
struct Book book1;
struct Book *ptr = &book1
// Using dot operator with struct variable
strcpy(book1.title, "C Programming");
book1.pages = 300;
book1.price = 29.99;
// Using arrow operator with pointer
printf("Title: %s\n", ptr->title);
printf("Pages: %d\n", ptr->pages);
printf("Price: $%.2f\n", ptr->price);
// Alternative: (*ptr).member
printf("Alternative access: %s\n", (*ptr).title);
return 0;
}
Output:
Title: C Programming Pages: 300 Price: $29.99 Alternative access: C Programming
🔹 Dynamic Memory Allocation
Allocating structures dynamically at runtime using malloc enables programs to adapt to varying data requirements and build flexible data structures. The pattern struct Node *node = malloc(sizeof(struct Node)); creates a structure on the heap that persists beyond the function scope where it was created. This is essential for linked lists, trees, graphs, and any data structure that grows or shrinks during program execution. Always verify malloc didn't return NULL before using the pointer, as allocation can fail when memory is exhausted. Pair each malloc with a corresponding free(node); to prevent memory leaks that gradually consume system resources. Dynamic allocation gives your programs the flexibility to handle real-world scenarios with unpredictable data sizes.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct Employee {
char name[30];
int id;
float salary;
};
int main() {
// Allocate memory for one Employee
struct Employee *emp = malloc(sizeof(struct Employee));
if(emp == NULL) {
printf("Memory allocation failed!\n");
return 1;
}
// Assign values
strcpy(emp->name, "John Doe");
emp->id = 1001;
emp->salary = 50000.0;
// Display information
printf("Employee Details:\n");
printf("Name: %s\n", emp->name);
printf("ID: %d\n", emp->id);
printf("Salary: $%.2f\n", emp->salary);
// Free allocated memory
free(emp);
emp = NULL; // Good practice
return 0;
}
Output:
Employee Details: Name: John Doe ID: 1001 Salary: $50000.00
🔹 Array of Struct Pointers
Creating arrays of structure pointers provides a flexible way to manage collections of dynamically allocated structures with efficient memory usage. You can declare an array like struct Student *class[50]; where each element can point to a dynamically allocated Student structure or be NULL. This approach allows you to allocate structures only when needed, avoiding wasted memory for unused array positions. It also enables efficient sorting by swapping pointers rather than copying entire structures. This pattern is ideal for applications like student databases, employee records, or inventory systems where the number of active entries varies. The combination of arrays and pointers provides both the convenience of indexed access and the flexibility of dynamic memory management.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct Student {
char name[25];
int roll;
float marks;
};
int main() {
int n = 3;
// Array of struct pointers
struct Student *students[3];
// Allocate memory for each student
for(int i = 0; i < n; i++) {
students[i] = malloc(sizeof(struct Student));
}
// Input data
strcpy(students[0]->name, "Alice");
students[0]->roll = 101;
students[0]->marks = 85.5;
strcpy(students[1]->name, "Bob");
students[1]->roll = 102;
students[1]->marks = 78.0;
strcpy(students[2]->name, "Carol");
students[2]->roll = 103;
students[2]->marks = 92.5;
// Display all students
printf("Student Records:\n");
for(int i = 0; i < n; i++) {
printf("%d. %s (Roll: %d) - %.1f%%\n",
i+1, students[i]->name,
students[i]->roll, students[i]->marks);
}
// Free memory
for(int i = 0; i < n; i++) {
free(students[i]);
}
return 0;
}
Output:
Student Records: 1. Alice (Roll: 101) - 85.5% 2. Bob (Roll: 102) - 78.0% 3. Carol (Roll: 103) - 92.5%
🔹 Passing Struct Pointers to Functions
Passing structure pointers to functions is the recommended approach for efficient parameter passing and enabling functions to modify caller's data. When you pass a structure by value, C copies the entire structure onto the function's stack, which is slow for large structures and prevents the function from modifying the original. Using void updatePerson(struct Person *p) passes only the 4 or 8-byte pointer regardless of structure size. Inside the function, use p->field to access members. Add const like void printPerson(const struct Person *p) when the function shouldn't modify the structure, providing compiler-enforced safety. This technique is crucial for professional C programming and is universally used in production codebases.
#include <stdio.h>
#include <string.h>
struct Car {
char brand[20];
char model[20];
int year;
float price;
};
// Function to display car details
void displayCar(struct Car *car) {
printf("Car Details:\n");
printf("Brand: %s\n", car->brand);
printf("Model: %s\n", car->model);
printf("Year: %d\n", car->year);
printf("Price: $%.2f\n", car->price);
}
// Function to update car price
void updatePrice(struct Car *car, float newPrice) {
car->price = newPrice;
printf("Price updated to $%.2f\n", newPrice);
}
int main() {
struct Car myCar;
// Initialize car data
strcpy(myCar.brand, "Toyota");
strcpy(myCar.model, "Camry");
myCar.year = 2022;
myCar.price = 25000.0;
// Display original details
displayCar(&myCar);
printf("\n");
// Update price
updatePrice(&myCar, 23000.0);
printf("\n");
// Display updated details
displayCar(&myCar);
return 0;
}
Output:
Car Details: Brand: Toyota Model: Camry Year: 2022 Price: $25000.00 Price updated to $23000.00 Car Details: Brand: Toyota Model: Camry Year: 2022 Price: $23000.00
🔹 Common Mistakes to Avoid
Avoiding common mistakes when working with structure pointers prevents crashes, memory leaks, and undefined behavior in your C programs. Never dereference a NULL pointer or an uninitialized pointer, as this causes segmentation faults. Always check if malloc succeeded before using the returned pointer. Don't forget to free dynamically allocated memory to prevent memory leaks. Be careful about dangling pointers that point to freed memory. Don't return pointers to local variables from functions, as they become invalid when the function returns. Understand the difference between shallow and deep copying when structures contain pointers. Use valgrind or similar tools to detect memory errors. These precautions ensure your programs are stable, reliable, and properly manage system resources.
Best Practices:
- Always check malloc return: Verify memory allocation succeeded
- Free allocated memory: Use free() to prevent memory leaks
- Set pointer to NULL: After freeing memory
- Initialize pointers: Don't use uninitialized pointers
- Use arrow operator: With pointers, not dot operator
// ❌ Wrong way
struct Person *ptr; // Uninitialized pointer
ptr->age = 25; // Dangerous!
// ✅ Correct way
struct Person *ptr = malloc(sizeof(struct Person));
if(ptr != NULL) {
ptr->age = 25; // Safe to use
free(ptr); // Don't forget to free
ptr = NULL; // Set to NULL after freeing
}