Docs

README

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

Navigation

README - C Programming Tutorial | DeepML