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
}

🧠 Test Your Knowledge

Which operator is used to access struct members through a pointer?