C ProgrammingUnit 812 min read

Pointers, Dynamic Memory & Memory Management in C

Unit 8 of C Programming covers pointers (addresses, dereferencing, pointer arithmetic), dynamic memory allocation (malloc, calloc, realloc, free), memory leaks, dangling pointers, and real-world applications in data structures and system-level programming.

TAKEAWAYS:

  • Pointers store memory addresses and enable indirect access to variables, enabling efficient data manipulation and dynamic memory handling.
  • Dynamic memory allocation (malloc, calloc) allows runtime memory allocation, while free deallocates it to prevent memory leaks.
  • Pointer arithmetic and arrays are deeply connected, enabling efficient traversal and manipulation of contiguous memory blocks.
  • Common pitfalls like dangling pointers, memory leaks, and wild pointers must be avoided for robust programs.
  • Dynamic memory is essential for implementing data structures like linked lists, trees, and graphs in C.
  • Understanding memory management is critical for writing efficient and secure programs, especially in system-level applications.

Pointers: The Foundation of Dynamic Memory

Pointers are variables that store memory addresses instead of values. They are fundamental in C for dynamic memory allocation, efficient data manipulation, and implementing complex data structures.

How Pointers Work

A pointer variable holds the address of another variable. To declare a pointer:

int *ptr;  // ptr is a pointer to an integer
  • * before a variable name declares it as a pointer.
  • & (address-of operator) gives the memory address of a variable.
  • * (dereference operator) accesses the value at the address stored in a pointer.

Example: Basic Pointer Operations

#include <stdio.h>

int main() {
    int x = 10;
    int *ptr = &x;  // ptr now holds the address of x

    printf("Address of x: %p\n", (void*)&x);
    printf("Value of ptr: %p\n", (void*)ptr);
    printf("Value of *ptr: %d\n", *ptr);  // Dereferencing ptr

    *ptr = 20;  // Changing x through ptr
    printf("New value of x: %d\n", x);

    return 0;
}

Trace Table:

Step x ptr (address) *ptr (value)
Initial 10 NULL -
ptr = &x 10 Address of x 10
*ptr = 20 20 Address of x 20

Pointer Arithmetic

Pointer arithmetic allows traversal of arrays and contiguous memory blocks. For example:

int arr[3] = {10, 20, 30};
int *ptr = arr;  // ptr points to the first element

```figure
{"type":"array","values":[10,20,30,40,50],"highlight":[1],"pointers":{"0":"ptr (points to arr[0])","1":"ptr + 1 (points to arr[1])"},"caption":"Pointer arithmetic: `ptr + 1` moves to the next integer (address +4 bytes)."}

printf("%d\n", *(ptr + 1)); // Output: 20 (equivalent to arr[1]) Key Rules:

  • Pointer arithmetic is valid only for pointers to elements of the same array or dynamically allocated contiguous memory.
  • ptr + 1 moves the pointer by sizeof(*ptr) bytes (e.g., 4 bytes for int on most systems).

Dynamic Memory Allocation

Dynamic memory allocation allows programs to request memory at runtime using functions from <stdlib.h>:

  • malloc: Allocates memory but does not initialize it.
  • calloc: Allocates and initializes memory to zero.
  • realloc: Resizes previously allocated memory.
  • free: Deallocates memory to avoid leaks.

Example: Allocating an Array Dynamically

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

```figure
{"type":"array","values":[1,2,3,null,null],"highlight":[0,1,2],"pointers":{"0":"arr (dynamically allocated)"},"caption":"State after `malloc(5 * sizeof(int))` and initialization: first 3 elements set to 1, 2, 3."}

int main() { int n, *arr;

printf("Enter size: ");
scanf("%d", &n);

// Allocate memory for n integers
arr = (int*)malloc(n * sizeof(int));
if (arr == NULL) {
    printf("Memory allocation failed!\n");
    return 1;
}

// Initialize and print
for (int i = 0; i < n; i++) {
    arr[i] = i + 1;
    printf("%d ", arr[i]);
}

// Free the allocated memory
free(arr);
return 0;

} Trace Table:

Step arr (address) Memory Contents (first 3)
After malloc Address X Garbage, Garbage, Garbage
After initialization Address X 1, 2, 3
After free Address X (Memory is freed)

Common Pitfalls and Best Practices

1. Memory Leaks

Occur when dynamically allocated memory is not freed, wasting system resources. Example:

int *ptr = malloc(10 * sizeof(int));
// Forgetting to free(ptr) causes a leak.

2. Dangling Pointers

Pointers that reference memory that has already been freed. Example:

int *ptr = malloc(sizeof(int));
free(ptr);
*ptr = 5;  // Undefined behavior (dangling pointer)

3. Wild Pointers

Pointers that are uninitialized or point to invalid memory. Example:

int *ptr;  // Wild pointer
printf("%d", *ptr);  // Undefined behavior

Best Practices:

  • Always check if malloc/calloc returns NULL.
  • Free memory when it is no longer needed.
  • Initialize pointers to NULL before use.
  • Use tools like Valgrind to detect memory leaks.

Pointers and Arrays

Arrays and pointers are closely related in C. The name of an array decays into a pointer to its first element. Example:

int arr[5] = {1, 2, 3, 4, 5};
int *ptr = arr;  // ptr points to arr[0]

printf("%d\n", *(ptr + 2));  // Output: 3 (equivalent to arr[2])

Key Insight:

graph LR
    A["int arr[5]"] --> B["&arr[0]"]
    B --> C["arr (decays to pointer)"]
    C --> D["ptr = arr"]
    D --> E["ptr + i = &arr[i]"]
    E --> F["*(ptr + i) = arr[i]"]

Real-World Applications

1. eSewa (Nepal)

  • Idea Used: Dynamic memory allocation for handling variable-sized transaction records.
  • How: eSewa processes thousands of transactions per second. Dynamic arrays (resizable using realloc) store transaction data efficiently, allowing the system to scale without predefined limits.

2. Khalti (Nepal)

  • Idea Used: Pointers for efficient data structure manipulation in fraud detection algorithms.
  • How: Khalti uses linked lists (implemented with pointers) to maintain a queue of pending transactions. Each transaction node contains a pointer to the next transaction, enabling efficient insertion and deletion.

3. Pathao (Nepal)

  • Idea Used: Dynamic memory for real-time route optimization.
  • How: Pathao’s algorithm dynamically allocates memory for graph nodes (representing locations) and edges (representing routes). Pointers are used to traverse the graph and find the shortest path using Dijkstra’s algorithm.

4. Bank Loan Calculations (Nepal)

  • Idea Used: Pointers for efficient computation of loan installments.
  • How: Banks use dynamic arrays to store loan schedules. For a loan of ₹1,000,000 at 10% interest over 5 years, the program dynamically allocates memory for 60 monthly installments and calculates each using pointer arithmetic:
    float *installments = malloc(60 * sizeof(float));
    float principal = 1000000.0;
    float rate = 0.10 / 12;
    for (int i = 0; i < 60; i++) {
        installments[i] = (principal * rate) / (1 - pow(1 + rate, -60));
    }
    

Data Structures Using Pointers

Pointers are essential for implementing dynamic data structures like linked lists, trees, and graphs.

Example: Singly Linked List

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

```mermaid
graph LR
    A[10] --> B[20]
    B --> C[30]
    style B fill:#f9f,stroke:#333
    caption: Linked list structure with `head` pointing to 10.

Pointer relationships in a singly linked list (head → 10 → 20 → 30).

head102030NULL
Linked list after inserting 20 between 10 and 30 (node `20` highlighted).

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

void insertEnd(struct Node **head, int data) { struct Node *newNode = malloc(sizeof(struct Node)); newNode->data = data; newNode->next = NULL;

if (*head == NULL) {
    *head = newNode;
} else {
    struct Node *temp = *head;
    while (temp->next != NULL) {
        temp = temp->next;
    }
    temp->next = newNode;
}

}

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

int main() { struct Node *head = NULL; insertEnd(&head, 10); insertEnd(&head, 20); insertEnd(&head, 30); printList(head); // Output: 10 -> 20 -> 30 -> NULL return 0; }

**State After Each Insertion:**
```mermaid
graph LR
    A["After insert 10"] --> B["10 -> NULL"]
    B --> C["After insert 20"]
    C --> D["10 -> 20 -> NULL"]
    D --> E["After insert 30"]
    E --> F["10 -> 20 -> 30 -> NULL"]

In the real world

  • Pathao (Nepal): Uses pointers to graph nodes in Dijkstra’s algorithm to dynamically update shortest paths between rider locations and pickup points. For example, when a rider requests a ride from Thapathali to Boudhanath, Pathao’s backend allocates memory for graph nodes (e.g., Thapathali, Kathmandu Durbar Square, Boudhanath) and edges (e.g., Thapathali → Durbar Square: 5 min), then traverses the graph using pointers to compute the optimal route.

  • Nepal Rastra Bank: Implements dynamic arrays with pointers to store loan installment schedules. For a ₹500,000 loan at 9% over 10 years, the bank’s system allocates memory for 120 installments (₹5,805.30 each) and uses pointer arithmetic to calculate each month’s principal/interest split.

  • Ncell (Nepal): Uses linked lists (pointer-based) to manage call logs. When a user makes a call to 01-4234567, Ncell appends a new node to the linked list (containing caller ID, timestamp, duration) and updates the head pointer. This allows efficient insertion/deletion without resizing arrays.

Exam Tip

What to Expect:

  1. Pointer Arithmetic Questions:

    • Expect questions on how pointer arithmetic works with arrays (e.g., *(ptr + i) vs. ptr[i]).
    • Example: Given int arr[3] = {5, 10, 15}; int *ptr = arr;, what does *(ptr + 1) + *(arr + 2) evaluate to? (Answer: 10 + 15 = 25).
  2. Dynamic Memory Allocation:

    • Questions may ask you to write code for allocating, initializing, and freeing memory.
    • Example: Write a function to allocate memory for an array of n integers and initialize all elements to 0.
  3. Debugging Pitfalls:

    • Identify memory leaks, dangling pointers, or wild pointers in given code snippets.
    • Example: Given a code snippet with malloc but no free, explain the memory leak.
  4. Pointers and Structures:

    • Questions on traversing or modifying linked lists, trees, or graphs using pointers.
    • Example: Write a function to reverse a singly linked list using pointers.
  5. Real-World Scenarios:

    • Apply concepts to practical scenarios like managing queues (e.g., Daraz order processing) or graphs (e.g., NTC traffic route optimization).
    • Example: Explain how dynamic memory allocation helps in implementing a priority queue for a hospital’s patient management system.

Key Formulas to Remember:

  • Size of dynamically allocated memory: n * sizeof(data_type).
  • Pointer arithmetic: ptr + i moves i * sizeof(*ptr) bytes.
  • Address of an array element: &arr[i] or arr + i.

Common Mistakes to Avoid:

  • Forgetting to free dynamically allocated memory.
  • Dereferencing NULL pointers or uninitialized pointers.
  • Assuming malloc always succeeds (always check for NULL).
  • Modifying pointers after freeing the memory they point to.

Final Worked Example: Bank Loan Amortization Schedule

Problem: Write a program to generate an amortization schedule for a loan of ₹500,000 at 8% annual interest over 5 years. Use dynamic memory to store monthly payments.

Solution:

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

void generateAmortizationSchedule(float principal, float rate, int years) {
    int months = years * 12;
    float monthlyRate = rate / 1200;  // Convert annual rate to monthly
    float monthlyPayment = (principal * monthlyRate) / (1 - pow(1 + monthlyRate, -months));

    // Allocate memory for the schedule
    float *schedule = malloc(months * sizeof(float));
    if (schedule == NULL) {
        printf("Memory allocation failed!\n");
        return;
    }

    float remaining = principal;
    printf("Month\tPayment\tPrincipal\tInterest\tRemaining\n");
    for (int i = 0; i < months; i++) {
        float interest = remaining * monthlyRate;
        float principalPart = monthlyPayment - interest;
        schedule[i] = remaining - principalPart;
        remaining = schedule[i];

        printf("%d\t%.2f\t%.2f\t\t%.2f\t\t%.2f\n",
               i + 1, monthlyPayment, principalPart, interest, remaining);
    }

    free(schedule);
}

int main() {
    generateAmortizationSchedule(500000, 8, 5);
    return 0;
}

Output (First 3 Months):

Month Payment Principal Interest Remaining
1 9,774.50 8,333.33 1,441.17 491,666.67
2 9,774.50 8,366.67 1,407.83 483,300.00
3 9,774.50 8,400.00 1,374.50 474,900.00

Key Insight:

  • The schedule array dynamically stores the remaining balance for each month.
  • Pointer arithmetic and dynamic memory ensure the program works for any loan term without predefined limits.

Based on the TU BIM syllabus for C Programming (IT232), unit 8.

Discussion

Loading…