BIT102 C Programming

C ProgrammingUnit 712 min read

Pointers and Dynamic Memory Allocation: Memory Manipulation and Efficiency

Unit 7 of C Programming: Explores pointers (variables holding memory addresses), their arithmetic, dynamic memory allocation (malloc, free), and their role in efficiency, arrays, and functions, with real-world applications in data structures and system programming.

TAKEAWAYS:

  • A pointer is a variable that stores a memory address, enabling indirect access to data and efficient memory operations.
  • Pointer arithmetic allows traversal of arrays and linked structures without index tracking.
  • Dynamic memory allocation (malloc, calloc, realloc) allocates memory at runtime, critical for scalable programs like databases or game engines.
  • Double pointers and pointers to functions enable advanced operations like sorting with callbacks or modifying function pointers in embedded systems.
  • Memory leaks and dangling pointers are common pitfalls; proper free() and null checks are mandatory.
  • Arrays and pointers are interchangeable in C due to their contiguous memory layout, a key optimization in low-level programming.

1. Introduction to Pointers

A pointer is a variable that holds the memory address of another variable. It allows indirect access to data, enabling efficient operations like dynamic memory management and complex data structures.

Why Use Pointers?

  • Efficiency: Avoid copying large data structures (e.g., arrays, structures).
  • Flexibility: Modify data in functions without returning values (e.g., swapping two numbers).
  • Dynamic Memory: Allocate memory at runtime (e.g., linked lists, trees).
  • Low-Level Control: Direct hardware interaction (e.g., embedded systems, device drivers).

Pointer Declaration and Initialization

A pointer is declared using the * operator. It must be initialized to a valid address or NULL to avoid undefined behavior.

flowchart TD
    A["int *ptr;"] --> B["Uninitialized pointer (dangerous)"]
    B --> C["int x = 10;"]
    C --> D["ptr = &x;"] --> E["ptr now holds address of x"]

Example: Declaration and Initialization

#include <stdio.h>

int main() {
    int num = 42;
    int *ptr = &num; // ptr holds address of num

    printf("Value of num: %d\n", num);       // 42
    printf("Address of num: %p\n", &num);    // Memory address
    printf("Value of ptr: %p\n", ptr);       // Same as &num
    printf("Value pointed by ptr: %d\n", *ptr); // 42 (dereferencing)

    return 0;
}

Output:

Value of num: 42
Address of num: 0x7ffd42a1b2ac
Value of ptr: 0x7ffd42a1b2ac
Value pointed by ptr: 42

Key Concepts

Term Definition Example
Pointer Variable Stores memory address of another variable. int *ptr;
Dereferencing Accesses the value at the address stored in a pointer. *ptr = 10;
Address-of Operator Returns the memory address of a variable (&). ptr = &num;
Null Pointer Pointer initialized to NULL (no valid address). int *ptr = NULL;

2. Pointer Arithmetic

Pointers can be incremented/decremented to traverse memory. The step size depends on the data type:

  • int *ptr: +4 bytes (32-bit system) or +8 bytes (64-bit).
  • char *ptr: +1 byte (since char is 1 byte).
flowchart TD
    A["int arr[3] = {10, 20, 30}"] --> B["&arr[0] (address of first element)"]
    B --> C["&arr[1] = &arr[0] + sizeof(int)"]
    C --> D["&arr[2] = &arr[0] + 2 * sizeof(int)"]
    D --> E["ptr++ moves by sizeof(int) bytes"]
    E --> F["Example: 32-bit system → +4 bytes per step"]

Example: Traversing an Array with Pointers

#include <stdio.h>

int main() {
    int arr[3] = {10, 20, 30};
    int *ptr = arr; // ptr points to arr[0]

    for (int i = 0; i < 3; i++) {
        printf("arr[%d] = %d\n", i, *(ptr + i)); // Dereference ptr+i
    }
    return 0;
}

Output:

arr[0] = 10
arr[1] = 20
arr[2] = 30

3. Pointers and Arrays

Arrays and pointers are interchangeable in C because arrays decay into pointers to their first element.

flowchart TD
    A["int arr[3] = {1, 2, 3}"] --> B["arr == &arr[0] (array decays to pointer)"]
    B --> C["ptr = arr; // ptr now holds address of arr[0]"]
    C --> D["*(ptr + 1) == arr[1] (equivalent to arr[1])"]
    D --> E["*(ptr + i) == arr[i] (general rule)"]

Example: Passing Arrays to Functions Using Pointers

#include <stdio.h>

void printArray(int *arr, int size) {
    for (int i = 0; i < size; i++) {
        printf("%d ", *(arr + i));
    }
}

int main() {
    int arr[3] = {10, 20, 30};
    printArray(arr, 3); // Passes pointer to arr
    return 0;
}

Output:

10 20 30

4. Pointers to Pointers (Double Pointers)

A double pointer holds the address of another pointer. Used for:

  • Modifying pointers inside functions.
  • Dynamic memory allocation (e.g., malloc returns a pointer, which may need modification).
flowchart TD
    A["int x = 5"] --> B["int *ptr = &x"]
    B --> C["int **dptr = &ptr"]
    C --> D["**dptr = 10; modifies x"]

Example: Using Double Pointers to Modify a Pointer

#include <stdio.h>

void modifyPointer(int **ptr) {
    **ptr = 100; // Modifies the original variable
}

int main() {
    int x = 5;
    int *ptr = &x;
    modifyPointer(&ptr); // Pass address of ptr
    printf("x = %d\n", x); // Output: 100
    return 0;
}

5. Dynamic Memory Allocation

C provides runtime memory allocation using:

  • malloc: Allocates uninitialized memory.
  • calloc: Allocates initialized memory (zeros).
  • realloc: Resizes previously allocated memory.
  • free: Deallocates memory to prevent leaks.
01234
Memory before and after `malloc(5 * sizeof(int))` (uninitialized memory)

Example: Dynamic Array Allocation

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

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

    arr[0] = 1; arr[1] = 2; arr[2] = 3;
    printf("Array elements: %d %d %d\n", arr[0], arr[1], arr[2]);

    free(arr); // Free memory
    return 0;
}

Output:

Array elements: 1 2 3

Common Pitfalls

Pitfall Explanation Solution
Memory Leak Forgetting to free() allocated memory. Always free() after use.
Dangling Pointer Pointer points to freed memory. Set to NULL after free().
Null Pointer Dereferencing NULL causes a crash. Check if (ptr != NULL) before use.

6. Pointers to Functions

A pointer to a function stores the address of a function, enabling:

  • Callbacks (e.g., sorting with custom comparators).
  • Function pointers in event-driven systems (e.g., GUI callbacks).
addsubtractmultiplydivide
Real-world function pointer applications in Nepalese software systems
flowchart TD
    A["int add(int a, int b) { return a + b; }"] --> B["int (*funcPtr)(int, int) = &add"]
    B --> C["funcPtr(2, 3) → calls add(2, 3)"]
    C --> D["Result: 5 (stored in return value)"]

Example: Function Pointer

#include <stdio.h>

int add(int a, int b) { return a + b; }
int subtract(int a, int b) { return a - b; }

int main() {
    int (*operation)(int, int) = add; // Function pointer
    printf("Result: %d\n", operation(5, 3)); // 8

    operation = subtract; // Switch to subtract
    printf("Result: %d\n", operation(5, 3)); // 2
    return 0;
}

Output:

Result: 8
Result: 2

7. Pointers and Strings

Strings in C are null-terminated character arrays. Pointers can traverse strings efficiently.

0123456'h''e''l''l''o''\0'
String traversal: `char *ptr` moves until `\0` is encountered

Example: String Length Using Pointers

#include <stdio.h>

int strlen(char *str) {
    int len = 0;
    while (*str != '\0') {
        len++;
        str++; // Move pointer
    }
    return len;
}

int main() {
    char str[] = "hello";
    printf("Length: %d\n", strlen(str)); // 5
    return 0;
}

Output:

Length: 5

In the Real World

  1. eSewa/Khalti (Digital Wallets)

    • Idea: Dynamic memory allocation is used to handle variable-length transaction records (e.g., storing user balances, transaction histories).
    • How: When a user adds a new transaction, the system dynamically allocates memory for the record (e.g., using malloc) and appends it to a linked list of transactions. This avoids fixed-size arrays and ensures scalability.
  2. Daraz (E-commerce Platform)

    • Idea: Pointers to functions are used in order processing pipelines.
    • How: Daraz uses function pointers to route orders to different handlers (e.g., process_payment(), update_inventory()). This modular design allows adding new features (e.g., discounts) without rewriting core logic.
  3. NTC/Ncell (Telecom Networks)

    • Idea: Double pointers manage call routing tables dynamically.
    • How: Telecom systems use double pointers to update routing tables in real time. For example, when a new cell tower is added, the system modifies the pointer-to-pointer structure to reflect the updated network topology.

Worked Example: Daraz Order Queue Suppose Daraz uses a queue to manage orders. Each order is stored in a dynamically allocated node:

typedef struct Order {
    int id;
    struct Order *next;
} Order;

Order *front = NULL, *rear = NULL;

void enqueue(int id) {
    Order *newOrder = (Order*)malloc(sizeof(Order));
    newOrder->id = id;
    newOrder->next = NULL;
    if (rear == NULL) {
        front = rear = newOrder;
    } else {
        rear->next = newOrder;
        rear = newOrder;
    }
}

State After Enqueueing Order 101:

front --> [101] --> NULL
         rear

When Order 101 is processed, front moves to the next node, and memory is freed to prevent leaks.


8. Exam Tips

  1. Pointer Declaration:

    • Always initialize pointers to NULL or a valid address. Uninitialized pointers lead to undefined behavior.
    • Example: int *ptr = NULL; (safe) vs. int *ptr; (dangerous).
  2. Dereferencing:

    • *ptr accesses the value at the address stored in ptr.
    • Common mistake: Forgetting the * when printing (printf("%d", ptr) prints an address, not the value).
  3. Dynamic Memory:

    • Check for malloc/calloc failures (if (ptr == NULL)).
    • Always free() memory to avoid leaks. Set pointers to NULL after freeing.
  4. Arrays vs. Pointers:

    • arr[i] is equivalent to *(arr + i).
    • When passing arrays to functions, use int *arr (not int arr[]), as arrays decay to pointers.
  5. Double Pointers:

    • Used to modify pointers inside functions (e.g., swapping two pointers).
    • Example: void swap(int **a, int **b) { int *temp = *a; *a = *b; *b = temp; }
  6. Function Pointers:

    • Syntax: return_type (*ptr)(args) = &function;
    • Useful for callbacks (e.g., sorting with qsort).
  7. Common Pitfalls:

    • Dangling Pointers: Accessing freed memory (e.g., free(ptr); *ptr = 5; is undefined).
    • Memory Leaks: Forgetting to free() allocated memory (e.g., in loops).
    • Null Pointer Dereference: if (ptr != NULL) { *ptr = 10; }

Sample Exam Question: Write a program to find the smallest element in an array using pointers. Solution:

#include <stdio.h>

int main() {
    int arr[] = {5, 2, 9, 1, 5};
    int *ptr = arr;
    int min = *ptr;

    for (int i = 1; i < 5; i++) {
        if (*(ptr + i) < min) {
            min = *(ptr + i);
        }
    }
    printf("Smallest element: %d\n", min);
    return 0;
}

Output:

Smallest element: 1

Visual Summary:

Based on the TU BIT syllabus for C Programming (BIT102), unit 7.

Discussion

Loading…