IT232 C Programming

C ProgrammingUnit 813 min read

Pointers, Dynamic Memory & Efficient Data Handling in C

Unit 8 of C Programming covers pointers (addresses, arithmetic, operations), dynamic memory allocation (malloc, calloc, realloc, free), and their applications in efficient data structures like linked lists and arrays. Learn how pointers enable flexible memory management and solve real-world problems like queue systems

TAKEAWAYS:

  • Pointers store memory addresses and enable direct memory access, enabling operations like array traversal and function argument passing by reference.
  • Dynamic memory allocation (malloc, calloc, realloc) allows runtime memory management, crucial for scalable data structures like linked lists and trees.
  • Pointer arithmetic lets you traverse arrays and strings efficiently, reducing loop overhead.
  • Memory leaks and dangling pointers are common pitfalls; always free allocated memory and validate pointers before use.
  • Pointers to pointers and pointers to functions enable advanced data structures (e.g., 2D arrays) and callback mechanisms.
  • Real-world applications include queue systems (e.g., Pathao ride requests), financial calculations (e.g., loan interest), and efficient data storage (e.g., Daraz product catalogs).

1. Introduction to Pointers

Pointers are variables that store memory addresses instead of values. They allow indirect access to data, enabling efficient memory manipulation and complex data structures.

Why Use Pointers?

  • Efficiency: Direct memory access reduces overhead in loops and recursive functions.
  • Flexibility: Enable dynamic data structures (e.g., linked lists, trees).
  • Functional Programming: Pass arguments by reference (modifying original data).
  • Hardware Interaction: Used in low-level programming (e.g., device drivers).

Pointer Basics

#include <stdio.h>

int main() {
    int num = 10;       // Variable declaration
    int *ptr = &num;    // Pointer stores address of 'num'
    printf("Value: %d\n", *ptr);  // Dereferencing: prints 10
    printf("Address: %p\n", ptr); // Prints memory address of 'num'
    return 0;
}

Trace Table:

Step num ptr *ptr Output
Declaration 10 (uninit) - -
ptr = &num 10 &num 10 -
printf(*ptr) 10 &num 10 Value: 10
printf(ptr) 10 &num 10 Address: 0x7ffd...

2. Pointer Arithmetic

Pointer arithmetic allows traversal of arrays and strings by adjusting memory addresses.

Key Rules:

  1. Increment/Decrement: Moves by the size of the data type (e.g., int* moves by 4 bytes, char* by 1 byte).
  2. No Random Access: Cannot add/subtract arbitrary values (e.g., ptr + 5 is valid only for arrays).
  3. Pointer Comparison: Valid for pointers to the same array or dynamically allocated memory.

Example: Array Traversal with Pointers

#include <stdio.h>

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

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

Visualization: Trace Table:

Step ptr *(ptr + i) Output
i=0 &arr[0] 10 10
i=1 &arr[0] 20 20
i=2 &arr[0] 30 30

3. Dynamic Memory Allocation

Static memory (e.g., arrays) has fixed size. Dynamic memory allocates memory at runtime using:

  • malloc: Allocates uninitialized memory.
  • calloc: Allocates and initializes to zero.
  • realloc: Resizes previously allocated memory.
  • free: Releases allocated memory to avoid leaks.

Example: Dynamic Array

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

```figure
{"type":"array","values":[10,20,30,40,50],"pointers":{"0":"start","4":"end"},"caption":"Dynamic array with pointers to track allocation bounds"}

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

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

// Resize to 5 elements
arr = (int*)realloc(arr, 5 * sizeof(int));
if (arr == NULL) {
    printf("Reallocation failed!\n");
    return 1;
}

arr[3] = 40; arr[4] = 50;

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

} Visualization (After Allocation):

Common Pitfalls:

Pitfall Cause Solution
Memory Leak Forgetting to free Always free after use
Dangling Pointer Using pointer after free Set pointer to NULL after free
Wild Pointer Uninitialized pointer Initialize pointers to NULL
Buffer Overflow Writing beyond allocated size Check bounds before access

4. Pointers and Functions

Pointers enable pass-by-reference, allowing functions to modify original data.

Example: Swapping Two Numbers

#include <stdio.h>

```figure
{"type":"network","nodes":["a","b","temp"],"edges":[["a","temp","1"],["b","temp","2"],["temp","a","3"],["temp","b","4"]],"directed":true,"caption":"Pointer flow during swap operation (steps 1-4)"}

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

int main() { int x = 5, y = 10; swap(&x, &y); // Pass addresses printf("x = %d, y = %d\n", x, y); // Output: x = 10, y = 5 return 0; } Visualization (Before/After Swap):


5. Pointers to Pointers

Used for multi-dimensional arrays or complex data structures (e.g., matrices).

Example: 2D Array with Pointers

#include <stdio.h>

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

    // Initialize
    matrix[0][0] = 1; matrix[0][1] = 2; matrix[0][2] = 3;
    matrix[1][0] = 4; matrix[1][1] = 5; matrix[1][2] = 6;

    // Free memory
    for (int i = 0; i < rows; i++) {
        free(matrix[i]);
    }
    free(matrix);
    return 0;
}

Visualization:


6. Pointers to Functions

Used for callback mechanisms (e.g., event handlers, sorting algorithms).

Example: Custom Sorting with Pointers to Functions

#include <stdio.h>

int ascending(const void *a, const void *b) {
    return (*(int*)a - *(int*)b);
}

int descending(const void *a, const void *b) {
    return (*(int*)b - *(int*)a);
}

int main() {
    int arr[3] = {30, 10, 20};
    int (*compare)(const void*, const void*) = &ascending;

    // Sort in ascending order
    qsort(arr, 3, sizeof(int), compare);
    printf("Ascending: %d %d %d\n", arr[0], arr[1], arr[2]);

    // Sort in descending order
    compare = &descending;
    qsort(arr, 3, sizeof(int), compare);
    printf("Descending: %d %d %d\n", arr[0], arr[1], arr[2]);
    return 0;
}

Output:

Ascending: 10 20 30
Descending: 30 20 10

In the Real World

  1. Pathao Ride Queue System
    • Idea Used: Dynamic Linked Lists
    • How: Pathao uses pointers to manage a queue of ride requests dynamically. Each new request is added to the end of the list (using malloc), and the driver picks the front request (using free after assignment). This ensures efficient memory usage and real-time updates.
    • Example:
headRequest 1Request 2Request 3NULL
Initial queue of ride requests (NULL-terminated)
 - When a driver accepts `Request 1`, the list updates to:
headRequest 2Request 3NULL
After Request 1 is dequeued (driver assignment)
  1. Khalti Transaction Processing

    • Idea Used: Pointer Arithmetic for Array Traversal
    • How: Khalti processes thousands of transactions per second. Pointers are used to traverse arrays of transaction records efficiently (e.g., transactions[ptr + i] instead of transactions[i] in loops), reducing overhead in high-frequency operations.
    • Example:
      // Pseudocode for transaction validation
      for (int i = 0; i < num_transactions; i++) {
          if (is_valid(*(transactions + i))) {
              process(*(transactions + i));
          }
      }
      
  2. Ncell Billing System (Loan Interest Calculation)

    • Idea Used: Pointers to Functions for Flexible Algorithms
    • How: Ncell’s billing system uses pointers to functions to apply different interest calculation methods (e.g., simple interest vs. compound interest) dynamically. The calculate_interest function takes a pointer to the interest function (e.g., simple_interest or compound_interest) as an argument.
    • Example:
      double simple_interest(double principal, double rate, int time) {
          return principal * rate * time / 100;
      }
      
      double (*interest_func)(double, double, int) = &simple_interest;
      double result = interest_func(1000, 5, 2);  // 100.00
      
  3. Daraz Product Catalog

    • Idea Used: Dynamic Memory Allocation for Scalable Storage
    • How: Daraz’s product database grows dynamically. Products are stored in linked lists or trees where each node is allocated using malloc. This avoids wasting memory on unused slots (unlike static arrays) and allows efficient insertion/deletion.
    • Example:
headProduct 1Product 2Product 3NULL
Initial product catalog (NULL-terminated)
 - When a new product is added:

7. Common Applications of Pointers

Application Use Case Example
Linked Lists Dynamic data structures Playlist management (Spotify)
Trees (Binary, AVL) Hierarchical data (e.g., file systems) Database indexing (SQLite)
Graphs Pathfinding (e.g., shortest path) Google Maps navigation
Function Pointers Event-driven programming GUI button click handlers
Memory-Efficient I/O Large file processing Video streaming (YouTube)

8. Exam Tip

What Examiners Look For:

  1. Correct Syntax:

    • Always declare pointers with * (e.g., int *ptr).
    • Use & for addresses and * for dereferencing.
    • Example of common mistakes:
      // Wrong: Missing &
      ptr = num;  // Error: Assigns value, not address
      
      // Correct:
      ptr = &num;
      
  2. Memory Management:

    • Allocate: ptr = (type*)malloc(size);
    • Free: free(ptr); (set ptr = NULL afterward).
    • Check for NULL: Always verify allocation success:
      if (ptr == NULL) {
          printf("Allocation failed!\n");
      }
      
  3. Pointer Arithmetic:

    • Only valid for arrays or contiguous memory blocks.
    • Example of valid vs. invalid:
      // Valid: Array traversal
      for (int i = 0; i < 3; i++) {
          printf("%d ", *(arr + i));
      }
      
      // Invalid: Random pointer arithmetic
      int *ptr = &num;
      ptr = ptr + 5;  // Undefined behavior!
      
  4. Real-World Scenarios:

    • Queues: Use pointers to manage dynamic FIFO structures (e.g., Pathao ride requests).
    • Financial Calculations: Pointers to functions for flexible algorithms (e.g., Ncell interest calculations).
    • Efficiency: Compare static vs. dynamic arrays in terms of memory usage and speed.

Sample Exam Questions and Answers:

Q1: How is pointer arithmetic used in array traversal? Illustrate with an example. A: Pointer arithmetic allows traversal by adjusting the pointer to the next memory location. For an array arr of size n, *(arr + i) accesses the i-th element without using indices.

int arr[3] = {1, 2, 3};
int *ptr = arr;
for (int i = 0; i < 3; i++) {
    printf("%d ", *(ptr + i));  // Prints 1, 2, 3
}

Q2: List one advantage and disadvantage of pointers. A:

  • Advantage: Enable dynamic memory allocation (e.g., linked lists) and efficient data structures.
  • Disadvantage: Risk of memory leaks or dangling pointers if not managed properly (e.g., forgetting free).

Q3: Explain how malloc and free work with an example. A:

  • malloc allocates memory at runtime. free releases it.
  • Example:
    int *arr = (int*)malloc(5 * sizeof(int));  // Allocates 5 ints
    if (arr == NULL) { /* Handle error */ }
    arr[0] = 10;
    free(arr);  // Releases memory
    arr = NULL; // Avoids dangling pointer
    

9. Practice Problems

  1. Write a program to reverse a string using pointers.
  2. Implement a dynamic array that doubles its size when full.
  3. Create a function to find the maximum of two numbers using pointers to functions.
  4. Explain how pointers enable efficient implementation of a stack (LIFO structure).
  5. What happens if you free a pointer twice? How can you prevent this?

10. Summary Table: Pointers vs. Non-Pointers

Feature Pointers Non-Pointers
Memory Access Direct (via address) Indirect (copy of value)
Efficiency Faster for large data structures Slower (value copying)
Flexibility Dynamic resizing (e.g., malloc) Fixed size (e.g., static arrays)
Risk Memory leaks, dangling pointers No risk (but limited functionality)
Use Case Linked lists, trees, I/O Simple variables, small arrays

11. Final Notes

  • Always initialize pointers to NULL to avoid wild pointers.
  • Use sizeof for dynamic allocation to ensure type safety.
  • Prefer calloc over malloc when initializing memory to zero.
  • Test edge cases: Empty allocations, free on NULL, and pointer overflow.

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

Discussion

Loading…