GuideC Programming

Pointers To Structures

Structures And Unions / Pointers To Structures

Concept Lesson
Intermediate
4 min

Learning Objective

Understand Pointers To Structures well enough to explain it, recognize it in C Programming, and apply it in a small task.

Why It Matters

This concept is part of the foundation that later lessons and projects assume you already understand.

StructuresTable Of ContentsWhy Use Structure PointersDeclaring Pointer To StructureBasic Declaration Syntax
Private notes
0/8000

Notes stay private to your browser until account sync is configured.

Guide
3 min read18 headings

Pointers to Structures in C

Table of Contents

  1. Introduction
  2. Declaring Pointer to Structure
  3. Initializing Structure Pointers
  4. The Arrow Operator (->)
  5. Arrow vs Dot Operator
  6. Dynamic Structure Allocation
  7. Passing Structures by Reference
  8. Linked Data Structures
  9. Self-Referential Structures
  10. Function Pointers in Structures
  11. Common Patterns
  12. Best Practices
  13. Summary

Introduction

Pointers to structures are one of the most powerful features in C programming. They enable:

  • Efficient function parameters: Pass large structures without copying
  • Dynamic data structures: Create linked lists, trees, graphs
  • Memory efficiency: Allocate structures at runtime
  • Object-oriented patterns: Simulate OOP concepts in C

Why Use Structure Pointers?

Without pointers: Copy entire structure (expensive for large structures)
With pointers: Copy only address (8 bytes on 64-bit systems)

struct Person person;     // 100 bytes
struct Person *ptr;       // 8 bytes (pointer)

Declaring Pointer to Structure

Basic Declaration Syntax

struct StructureName *pointerName;

Examples

// First, define a structure
struct Person {
    char name[50];
    int age;
    float salary;
};

// Declare pointer to structure
struct Person *personPtr;

// With typedef
typedef struct {
    int x;
    int y;
} Point;

Point *pointPtr;

Visual Representation

struct Person person = {"John", 25, 50000.0};
struct Person *ptr = &person;

Memory Layout:
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                 person (struct)                β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚
β”‚  β”‚ name[50]     β”‚ age   β”‚ salary          β”‚   β”‚
β”‚  β”‚ "John"       β”‚ 25    β”‚ 50000.0         β”‚   β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚
β”‚  Address: 0x1000                               β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
          ↑
          β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ ptr             β”‚
β”‚ 0x1000          β”‚
β”‚ Address: 0x2000 β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Initializing Structure Pointers

Method 1: Address of Existing Structure

struct Person person = {"Alice", 30, 60000.0};
struct Person *ptr = &person;  // Point to existing structure

Method 2: Dynamic Allocation

struct Person *ptr = malloc(sizeof(struct Person));
// Don't forget to check for NULL and free later!

Method 3: Array Element Address

struct Person people[10];
struct Person *ptr = &people[0];  // Or just: ptr = people;
struct Person *ptr2 = &people[5]; // Point to 6th element

Method 4: Function Return

struct Person* createPerson(const char *name, int age) {
    struct Person *p = malloc(sizeof(struct Person));
    if (p != NULL) {
        strcpy(p->name, name);
        p->age = age;
    }
    return p;
}

struct Person *ptr = createPerson("Bob", 25);

The Arrow Operator (->)

Introduction

The arrow operator -> is used to access structure members through a pointer. It combines dereferencing and member access.

Syntax

pointer->member

Equivalence

ptr->member  ≑  (*ptr).member

Why Arrow Operator Exists

// Without arrow operator (cumbersome)
(*ptr).name
(*ptr).age
(*ptr).salary

// With arrow operator (clean)
ptr->name
ptr->age
ptr->salary

Practical Example

struct Person {
    char name[50];
    int age;
    float salary;
};

struct Person person = {"Charlie", 35, 75000.0};
struct Person *ptr = &person;

// Using arrow operator
printf("Name: %s\n", ptr->name);      // Charlie
printf("Age: %d\n", ptr->age);        // 35
printf("Salary: %.2f\n", ptr->salary); // 75000.00

// Modifying through pointer
ptr->age = 36;
ptr->salary = 80000.0;

Arrow vs Dot Operator

Comparison Table

OperatorUsed WithSyntaxExample
. (dot)Structure variablestruct.memberperson.name
-> (arrow)Pointer to structureptr->memberptr->name

When to Use Which

struct Person person;        // Structure variable
struct Person *ptr = &person; // Pointer to structure

// Use dot with structure variable
person.age = 25;

// Use arrow with pointer
ptr->age = 25;

// Alternative (not recommended)
(*ptr).age = 25;  // Same as ptr->age

Visual Comparison

Direct Access (dot):          Indirect Access (arrow):
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”           β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ person          β”‚           β”‚ ptr             │──────┐
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚           β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜      β”‚
β”‚ β”‚ age: 25     β”‚ β”‚                                    β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚           β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”      β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜           β”‚ person          β”‚β†β”€β”€β”€β”€β”€β”˜
  person.age = 25             β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
                              β”‚ β”‚ age: 25     β”‚ β”‚
                              β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
                              β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                ptr->age = 25

Dynamic Structure Allocation

Single Structure Allocation

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct Person {
    char name[50];
    int age;
};

int main() {
    // Allocate memory for one structure
    struct Person *ptr = malloc(sizeof(struct Person));

    if (ptr == NULL) {
        fprintf(stderr, "Memory allocation failed\n");
        return 1;
    }

    // Initialize using arrow operator
    strcpy(ptr->name, "David");
    ptr->age = 40;

    // Use the structure
    printf("Name: %s, Age: %d\n", ptr->name, ptr->age);

    // Free memory when done
    free(ptr);
    ptr = NULL;  // Prevent dangling pointer

    return 0;
}

Array of Structures Allocation

int n = 5;
struct Person *people = malloc(n * sizeof(struct Person));

if (people != NULL) {
    // Access using array notation or pointer arithmetic
    for (int i = 0; i < n; i++) {
        sprintf(people[i].name, "Person%d", i + 1);
        people[i].age = 20 + i;

        // Or using pointer arithmetic:
        // (people + i)->age = 20 + i;
    }

    free(people);
}

Memory Layout

struct Person *people = malloc(3 * sizeof(struct Person));

Memory:
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   people[0]     β”‚   people[1]     β”‚   people[2]     β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β” β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β” β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚name   β”‚ age β”‚ β”‚ β”‚name   β”‚ age β”‚ β”‚ β”‚name   β”‚ age β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”˜ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”˜ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”˜ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
  ↑                 ↑                 ↑
  people           people+1          people+2

Passing Structures by Reference

Pass by Value vs Pass by Reference

// Pass by Value - Copies entire structure
void updateByValue(struct Person p) {
    p.age = 100;  // Only modifies copy!
}

// Pass by Reference - Passes pointer
void updateByReference(struct Person *p) {
    p->age = 100;  // Modifies original!
}

Performance Comparison

struct LargeStruct {
    int data[1000];
    char buffer[5000];
};

// BAD: Copies 24000+ bytes
void processByValue(struct LargeStruct s) { ... }

// GOOD: Copies only 8 bytes (pointer)
void processByReference(struct LargeStruct *s) { ... }

// BEST: Use const for read-only access
void printByReference(const struct LargeStruct *s) { ... }

Complete Example

#include <stdio.h>
#include <string.h>

struct Employee {
    char name[50];
    int id;
    float salary;
};

// Read-only access (use const)
void printEmployee(const struct Employee *emp) {
    printf("ID: %d, Name: %s, Salary: %.2f\n",
           emp->id, emp->name, emp->salary);
}

// Modify structure
void giveRaise(struct Employee *emp, float percent) {
    emp->salary *= (1.0 + percent / 100.0);
}

// Initialize structure
void initEmployee(struct Employee *emp, int id,
                  const char *name, float salary) {
    emp->id = id;
    strncpy(emp->name, name, sizeof(emp->name) - 1);
    emp->name[sizeof(emp->name) - 1] = '\0';
    emp->salary = salary;
}

int main() {
    struct Employee emp;

    initEmployee(&emp, 101, "John Doe", 50000.0);
    printEmployee(&emp);

    giveRaise(&emp, 10);  // 10% raise
    printf("After raise:\n");
    printEmployee(&emp);

    return 0;
}

Linked Data Structures

Introduction to Linked Lists

A linked list uses structure pointers to connect nodes:

struct Node {
    int data;
    struct Node *next;  // Pointer to same type
};

Visual Representation

Linked List: 10 -> 20 -> 30 -> NULL

β”Œβ”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”   β”Œβ”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”   β”Œβ”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”
β”‚  10  β”‚ next ─┼──→│  20  β”‚ next ─┼──→│  30  β”‚ NULL  β”‚
β””β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”˜   β””β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”˜   β””β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”˜
  ↑
 head

Basic Linked List Implementation

#include <stdio.h>
#include <stdlib.h>

struct Node {
    int data;
    struct Node *next;
};

// Create a new node
struct Node* createNode(int data) {
    struct Node *newNode = malloc(sizeof(struct Node));
    if (newNode != NULL) {
        newNode->data = data;
        newNode->next = NULL;
    }
    return newNode;
}

// Insert at beginning
struct Node* insertFront(struct Node *head, int data) {
    struct Node *newNode = createNode(data);
    if (newNode != NULL) {
        newNode->next = head;
        head = newNode;
    }
    return head;
}

// Print list
void printList(struct Node *head) {
    struct Node *current = head;
    while (current != NULL) {
        printf("%d -> ", current->data);
        current = current->next;
    }
    printf("NULL\n");
}

// Free list
void freeList(struct Node *head) {
    struct Node *current = head;
    while (current != NULL) {
        struct Node *next = current->next;
        free(current);
        current = next;
    }
}

int main() {
    struct Node *head = NULL;

    head = insertFront(head, 30);
    head = insertFront(head, 20);
    head = insertFront(head, 10);

    printList(head);  // Output: 10 -> 20 -> 30 -> NULL

    freeList(head);
    return 0;
}

Self-Referential Structures

Definition

A self-referential structure contains a pointer to its own type:

struct Node {
    int data;
    struct Node *next;  // Self-reference
};

Common Self-Referential Structures

// Singly Linked List Node
struct SLLNode {
    int data;
    struct SLLNode *next;
};

// Doubly Linked List Node
struct DLLNode {
    int data;
    struct DLLNode *prev;
    struct DLLNode *next;
};

// Binary Tree Node
struct TreeNode {
    int data;
    struct TreeNode *left;
    struct TreeNode *right;
};

// Graph Node (Adjacency List)
struct GraphNode {
    int vertex;
    struct GraphNode *next;
};

Binary Tree Example

struct TreeNode {
    int data;
    struct TreeNode *left;
    struct TreeNode *right;
};

struct TreeNode* createTreeNode(int data) {
    struct TreeNode *node = malloc(sizeof(struct TreeNode));
    if (node != NULL) {
        node->data = data;
        node->left = NULL;
        node->right = NULL;
    }
    return node;
}

// Build a simple tree
//       5
//      / \
//     3   7
struct TreeNode *root = createTreeNode(5);
root->left = createTreeNode(3);
root->right = createTreeNode(7);

Function Pointers in Structures

Simulating Object-Oriented Programming

#include <stdio.h>
#include <string.h>

// Structure with function pointers
struct Calculator {
    int value;
    void (*add)(struct Calculator *self, int n);
    void (*subtract)(struct Calculator *self, int n);
    void (*display)(const struct Calculator *self);
};

// Method implementations
void calc_add(struct Calculator *self, int n) {
    self->value += n;
}

void calc_subtract(struct Calculator *self, int n) {
    self->value -= n;
}

void calc_display(const struct Calculator *self) {
    printf("Value: %d\n", self->value);
}

// Constructor-like function
void initCalculator(struct Calculator *calc, int initial) {
    calc->value = initial;
    calc->add = calc_add;
    calc->subtract = calc_subtract;
    calc->display = calc_display;
}

int main() {
    struct Calculator calc;
    initCalculator(&calc, 10);

    calc.display(&calc);      // Value: 10
    calc.add(&calc, 5);
    calc.display(&calc);      // Value: 15
    calc.subtract(&calc, 3);
    calc.display(&calc);      // Value: 12

    return 0;
}

Virtual Table Pattern

// Operation interface
struct Operations {
    int (*calculate)(int a, int b);
    const char* (*getName)(void);
};

// Addition operations
int add_calc(int a, int b) { return a + b; }
const char* add_name(void) { return "Addition"; }

struct Operations addOps = {add_calc, add_name};

// Multiplication operations
int mul_calc(int a, int b) { return a * b; }
const char* mul_name(void) { return "Multiplication"; }

struct Operations mulOps = {mul_calc, mul_name};

// Use through pointer
void performOperation(struct Operations *ops, int a, int b) {
    printf("%s of %d and %d = %d\n",
           ops->getName(), a, b, ops->calculate(a, b));
}

int main() {
    performOperation(&addOps, 5, 3);  // Addition of 5 and 3 = 8
    performOperation(&mulOps, 5, 3);  // Multiplication of 5 and 3 = 15
    return 0;
}

Common Patterns

Pattern 1: Factory Function

struct Person* Person_create(const char *name, int age) {
    struct Person *p = malloc(sizeof(struct Person));
    if (p != NULL) {
        strncpy(p->name, name, sizeof(p->name) - 1);
        p->name[sizeof(p->name) - 1] = '\0';
        p->age = age;
    }
    return p;
}

void Person_destroy(struct Person *p) {
    free(p);
}

Pattern 2: Opaque Pointer

// In header file (person.h)
typedef struct Person Person;

Person* Person_create(const char *name, int age);
void Person_destroy(Person *p);
const char* Person_getName(const Person *p);
int Person_getAge(const Person *p);

// In source file (person.c)
struct Person {
    char name[50];
    int age;
};

// Implementation hidden from users

Pattern 3: Handle-Based API

typedef struct {
    int id;
    void *internal;  // Implementation details hidden
} Handle;

Handle* createHandle(void);
void destroyHandle(Handle *h);
int performOperation(Handle *h, int param);

Best Practices

1. Always Check malloc Return Value

// GOOD
struct Person *p = malloc(sizeof(struct Person));
if (p == NULL) {
    fprintf(stderr, "Memory allocation failed\n");
    return NULL;
}

// BAD
struct Person *p = malloc(sizeof(struct Person));
p->age = 25;  // Crashes if malloc failed!

2. Use sizeof with Variable, Not Type

// GOOD - Adapts if type changes
struct Person *p = malloc(sizeof(*p));

// OK but less flexible
struct Person *p = malloc(sizeof(struct Person));

3. Nullify Pointers After Free

free(ptr);
ptr = NULL;  // Prevent dangling pointer access

4. Use const for Read-Only Access

// Function won't modify the structure
void printPerson(const struct Person *p) {
    printf("%s, %d\n", p->name, p->age);
}

5. Document Ownership

// Caller owns returned pointer (must free)
struct Person* createPerson(void);

// Function takes ownership (will free internally)
void destroyPerson(struct Person *p);

// Function borrows pointer (caller keeps ownership)
void printPerson(const struct Person *p);

6. Handle Nested Pointers Carefully

struct Person {
    char *name;  // Dynamically allocated
    int age;
};

void freePerson(struct Person *p) {
    if (p != NULL) {
        free(p->name);  // Free nested first
        free(p);        // Then free structure
    }
}

Common Mistakes to Avoid

Mistake 1: Using Dot Instead of Arrow

struct Person *ptr = &person;
ptr.age = 25;   // ERROR! Use -> with pointers
ptr->age = 25;  // CORRECT

Mistake 2: Forgetting to Allocate Memory

struct Person *ptr;
ptr->age = 25;  // ERROR! ptr is uninitialized/dangling

// CORRECT
struct Person *ptr = malloc(sizeof(struct Person));
if (ptr != NULL) {
    ptr->age = 25;
}

Mistake 3: Not Freeing Nested Allocations

struct Person {
    char *name;
};

// WRONG - Memory leak
free(person);  // name is leaked!

// CORRECT
free(person->name);
free(person);

Mistake 4: Using Freed Pointer

free(ptr);
printf("%d\n", ptr->age);  // UNDEFINED BEHAVIOR!

// CORRECT
free(ptr);
ptr = NULL;

Summary

Key Concepts

ConceptDescription
Structure PointerPointer variable that stores address of a structure
Arrow Operator (->)Access member through pointer: ptr->member
Dynamic AllocationCreate structures at runtime with malloc
Pass by ReferenceEfficient function parameter passing
Self-ReferentialStructure containing pointer to same type
Linked StructuresDynamic data structures using pointers

Essential Equivalences

ptr->member  ≑  (*ptr).member
ptr[i]       ≑  *(ptr + i)
&ptr->member ≑  &((*ptr).member)

Memory Management Checklist

  1. ☐ Check malloc return value
  2. ☐ Initialize all members
  3. ☐ Free in reverse order of allocation
  4. ☐ Set pointer to NULL after free
  5. ☐ Free nested pointers first
  6. ☐ Document ownership clearly

Skill Check

Test this lesson

Answer 4 quick questions to lock in the lesson and feed your adaptive practice queue.

--
Score
0/4
Answered
Not attempted
Status
1

Which module does this lesson belong to?

2

Which section is covered in this lesson content?

3

Which term is most central to this lesson?

4

What is the best way to use this lesson for real learning?

Your answers save locally first, then sync when account storage is available.
Practice queue