Elective Programming In C

Programming In CUnit 89 min read

Pointers, Dynamic Memory & Memory Management in C

Unit 8 of Programming In C covers pointers (addresses, pointer arithmetic, pointer-to-pointer), dynamic memory allocation (malloc, calloc, realloc, free), memory leaks, dangling pointers, and practical applications like linked lists, flexible data structures, and efficient array handling.

TAKEAWAYS:

  • Pointers store memory addresses and enable direct memory access, enabling efficient array traversal, dynamic memory management, and function parameter passing.
  • Dynamic memory allocation (malloc, calloc, realloc, free) allows runtime memory allocation, crucial for scalable programs like databases or large-scale simulations.
  • Memory leaks and dangling pointers are common pitfalls; always pair malloc/calloc with free and validate pointers before dereferencing.
  • Pointer arithmetic (ptr + n, ptr++) works on byte offsets, not logical increments, making it essential for low-level memory manipulation.
  • Dynamic memory management is used in real-world applications like Khalti’s transaction logs (flexible memory for variable-sized records) and Pathao’s ride-matching system (dynamic allocation for real-time driver-passenger pairing).

1. Pointers: The Foundation of Memory Access

1.1 What is a Pointer?

A pointer is a variable that stores the memory address of another variable. It enables indirect access to data, allowing efficient memory manipulation.

int x = 10;      // Integer variable
int *ptr = &x;   // Pointer storing address of x
  • &x → Address-of operator (returns memory address of x).
  • *ptr → Dereference operator (accesses value at address stored in ptr).

1.2 Why Use Pointers?

Feature Without Pointers With Pointers
Memory Efficiency Copies data (inefficient for large data) Works with addresses (direct access)
Dynamic Allocation Fixed-size arrays only Allocate/deallocate memory at runtime
Function Parameters Pass-by-value (copies) Pass-by-reference (modifies original)
Data Structures Limited to static arrays Enables linked lists, trees, graphs

1.3 Pointer Arithmetic

Pointers can be incremented/decremented to traverse memory locations. The operation depends on the data type of the pointer:

  • ptr + 1 → Moves to the next memory location of the same type (e.g., int* moves by 4 bytes, char* by 1 byte).
  • ptr++ → Increments the pointer to the next element.
int arr[3] = {10, 20, 30};
int *ptr = arr;  // Points to first element

printf("%d", *(ptr + 1));  // Output: 20 (skips 1 int-sized step)
ptr++;                    // Now points to 20

Visual: Pointer Arithmetic in an Array

1.4 Pointer-to-Pointer (Double Pointer)

A pointer that stores the address of another pointer. Used for:

  • Modifying pointers inside functions.
  • Creating 2D arrays dynamically.
int x = 100;
int *ptr = &x;
int **pptr = &ptr;  // pptr stores address of ptr

printf("%d", **pptr);  // Output: 100 (dereferences twice)

Real-World Example: Khalti’s Transaction Logs Khalti uses pointer-to-pointer to dynamically manage transaction records. Each transaction is stored in a linked list where:

  • The head pointer (ptr) points to the first transaction.
  • A double pointer (**pptr) allows the system to update the head when new transactions arrive (e.g., during peak hours like Dashain).

2. Dynamic Memory Allocation

Static memory (e.g., arrays) has fixed size. Dynamic memory allows allocation/deallocation at runtime using:

  • malloc() → Allocates uninitialized memory.
  • calloc() → Allocates and initializes to zero.
  • realloc() → Resizes previously allocated memory.
  • free() → Releases allocated memory.

2.1 malloc() vs calloc()

Function Syntax Initialization Use Case
malloc ptr = (type*)malloc(size) Uninitialized General-purpose allocation
calloc ptr = (type*)calloc(n, size) Zero-initialized Arrays, structures needing defaults
int *arr1 = (int*)malloc(5 * sizeof(int));  // Uninitialized
int *arr2 = (int*)calloc(5, sizeof(int));   // All zeros

2.2 Memory Leaks and Dangling Pointers

  • Memory Leak: Forgetting to free() allocated memory.
    int *ptr = malloc(sizeof(int));
    ptr = NULL;  // Leak! Original memory lost.
    
  • Dangling Pointer: Pointer referencing freed memory.
    int *ptr = malloc(sizeof(int));
    free(ptr);
    printf("%d", *ptr);  // Undefined behavior!
    

Visual: Memory Leak in a Program

flowchart TD
    A["Allocate 100 bytes"] --> B["Use memory"]
    B --> C["Forget to free()"]
    C --> D["Memory Leak: 100 bytes lost"]

2.3 Real-World Example: Daraz’s Order Queue

Daraz uses dynamic memory allocation to handle variable-sized order queues during sales (e.g., Dashain discounts). Instead of preallocating a fixed-size array, it:

  1. Allocates memory for orders dynamically using malloc.
  2. Uses realloc to resize the queue if orders exceed capacity.
  3. Frees memory after order processing to avoid leaks.

Code Example: Dynamic Array for Orders

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

int main() {
    int *orders = NULL;
    int size = 0, capacity = 2;

    // Simulate adding orders dynamically
    orders = (int*)malloc(capacity * sizeof(int));
    for (int i = 0; i < 5; i++) {
        if (i >= capacity) {
            capacity *= 2;
            orders = (int*)realloc(orders, capacity * sizeof(int));
        }
        orders[i] = i + 1;  // Order ID
    }

    // Display orders
    for (int i = 0; i < 5; i++) {
        printf("Order %d: %d\n", i + 1, orders[i]);
    }

    free(orders);  // Prevent memory leak
    return 0;
}

Output:

Order 1: 1
Order 2: 2
Order 3: 3
Order 4: 4
Order 5: 5

3. Pointers and Arrays: The Connection

Arrays decay into pointers when passed to functions. This is why:

  • arr and &arr[0] are equivalent.
  • sizeof(arr) in a function is not the array size (it becomes a pointer).
void printArray(int *arr, int size) {
    for (int i = 0; i < size; i++) {
        printf("%d ", *(arr + i));  // Equivalent to arr[i]
    }
}

Visual: Array and Pointer Relationship


4. Pointers in Functions: Pass-by-Reference

Functions can modify original variables using pointers.

void swap(int *a, int *b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

Real-World Example: Ncell’s Billing System Ncell’s billing system uses pointers to modify customer data directly without copying entire records. For example:

  • A function updateBalance(int *balance, int amount) adjusts a customer’s balance in-place.
  • Avoids inefficient copying of large customer databases.

5. Common Pitfalls and Best Practices

Pitfall Solution
Uninitialized Pointer Always initialize pointers (NULL).
Dangling Pointer Set pointers to NULL after free().
Memory Leak Use free() for every malloc/calloc.
Pointer Arithmetic Errors Cast pointers explicitly when needed.
Double Free Track allocations with a linked list.

Best Practice: Always Validate Pointers

if (ptr != NULL) {
    printf("%d", *ptr);
} else {
    printf("Error: Null pointer!");
}

6. Dynamic Memory for Data Structures

6.1 Linked Lists

Nodes are dynamically allocated and linked using pointers.

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

Node *head = NULL;
Node *newNode = (Node*)malloc(sizeof(Node));
newNode->data = 10;
newNode->next = head;
head = newNode;

Visual: Linked List After Insertion

6.2 2D Arrays Dynamically

int **matrix = (int**)malloc(rows * sizeof(int*));
for (int i = 0; i < rows; i++) {
    matrix[i] = (int*)malloc(cols * sizeof(int));
}

Real-World Example: NTC’s Traffic Route Optimization NTC uses dynamic 2D arrays to represent Kathmandu’s traffic grid. Each cell stores:

  • Traffic density (updated in real-time).
  • Pointers to neighboring intersections for pathfinding.

7. Exam Tip: How to Score Full Marks

  1. Always explain the purpose of pointers/dynamic memory (e.g., "Pointers enable efficient memory access without copying data").
  2. Show step-by-step traces for pointer arithmetic or dynamic allocation (e.g., "After ptr++, the pointer moves to the next int location").
  3. Compare malloc vs calloc in tables (as shown above).
  4. Link to real-world systems (e.g., "Khalti uses dynamic memory for transaction logs").
  5. Avoid common mistakes:
    • Forgetting to free() memory.
    • Dereferencing NULL pointers.
    • Misusing pointer arithmetic (e.g., char* vs int* increments).

Sample Exam Question Solution: Q: Write a program to input n numbers in an array using dynamic memory and find the average. A:

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

int main() {
    int n, *arr, sum = 0;
    float avg;

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

    arr = (int*)malloc(n * sizeof(int));  // Dynamic allocation
    printf("Enter %d numbers:\n", n);
    for (int i = 0; i < n; i++) {
        scanf("%d", &arr[i]);
        sum += arr[i];
    }

    avg = (float)sum / n;
    printf("Average: %.2f\n", avg);

    free(arr);  // Prevent memory leak
    return 0;
}

Trace for n = 3, Input: 10 20 30

Step arr (Memory) sum avg
Allocation [10, 20, 30] 0 -
Loop 1 [10, 20, 30] 10 -
Loop 2 [10, 20, 30] 30 -
Loop 3 [10, 20, 30] 60 20.00
Free Memory released 60 20.00

memory allocation diagram**How dynamic memory works in C programs (Image: Siddharthist, CC BY-SA 4.0, via Wikimedia Commons) linked list data structure**Visual representation of nodes and pointers (Image: Michel Bakni, CC BY-SA 4.0, via Wikimedia Commons)

Based on the PU BE Computer (PU) syllabus for Programming In C, unit 8.

Discussion

Loading…