UPES - Cloud & Software Operations Cluster (SOCS)
BTech First Semester | C Programming

Lecture 22

Garbage Collection &
Dynamic Memory Management

Unit V: File Handling, Memory Management

Instructor: Mohsin F. Dar
Assistant Professor
Cloud & Software Operations Cluster | SOCS | UPES

Quick Recap

Dynamic Memory Management in C

Key Point: In C, the programmer is responsible for both allocation and deallocation of memory. There is NO automatic garbage collection!

What is Garbage Collection?

Definition

Garbage Collection (GC) is an automatic memory management feature that identifies and reclaims memory occupied by objects that are no longer in use by the program.

In Simple Terms:

It's like having an automatic cleaner that finds and removes unused items from your computer's memory.

Languages WITH GC

  • Java
  • Python
  • JavaScript
  • C#
  • Go

Languages WITHOUT GC

  • C
  • C++
  • Rust (ownership model)

Why C Doesn't Have Garbage Collection?

Design Philosophy

Trade-off: Greater power and performance comes with greater responsibility. The programmer must manually manage memory, which can lead to bugs if not done carefully.

Memory Leaks

What is a Memory Leak?

A memory leak occurs when dynamically allocated memory is not freed, causing the program to consume more and more memory over time.

Example of Memory Leak:

void createLeak() {
    int *ptr = (int*) malloc(sizeof(int) * 100);
    // Memory allocated but never freed!
    // When function returns, pointer is lost
    // Memory becomes inaccessible (leaked)
}

int main() {
    for(int i = 0; i < 1000; i++) {
        createLeak(); // Leaking memory 1000 times!
    }
    return 0;
}
Consequence: Program gradually consumes all available memory, leading to crashes or system slowdown.

Dangling Pointers

What is a Dangling Pointer?

A dangling pointer is a pointer that references memory that has already been freed or is invalid.

Example of Dangling Pointer:

int main() {
    int *ptr = (int*) malloc(sizeof(int));
    *ptr = 42;

    free(ptr); // Memory freed

    // ptr is now dangling - points to freed memory!
    printf("%d", *ptr); // Undefined behavior!

    return 0;
}
Danger: Accessing dangling pointers can cause crashes, data corruption, or security vulnerabilities!
Best Practice: Set pointer to NULL after freeing: ptr = NULL;

Double Free Error

What is Double Free?

A double free occurs when free() is called twice on the same memory address.

Example of Double Free:

int main() {
    int *ptr = (int*) malloc(sizeof(int));
    *ptr = 100;

    free(ptr); // First free - OK
    free(ptr); // Second free - ERROR!

    return 0; }
Consequence: Heap corruption, program crash, or security vulnerabilities.

Prevention:

free(ptr);
ptr = NULL; // Setting to NULL prevents double free

// Later, you can safely check:
if(ptr != NULL) {
    free(ptr);
}

Best Practices for Memory Management

Golden Rules

  1. Always check if malloc/calloc succeeded
    int *ptr = (int*) malloc(sizeof(int));
    if(ptr == NULL) {
        fprintf(stderr, "Memory allocation failed\n");
        return -1;
    }
  2. Every malloc needs a corresponding free
  3. Set pointers to NULL after freeing
  4. Never access memory after freeing it
  5. Free memory in reverse order of dependencies

Problem 1: Dynamic Array

Problem:

Create a program that dynamically allocates an array of integers, fills it with user input, displays the values, and properly frees the memory.

Solution:

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

int main() {
    int n, i;
    int *arr;
    
    printf("Enter number of elements: ");
    scanf("%d", &n);
    
    // Allocate memory
    arr = (int*) malloc(n * sizeof(int));
    
    // Check allocation
    if(arr == NULL) {
        printf("Memory allocation failed!\n");
        return -1;
    }
    
    // Input values
    for(i = 0; i < n; i++) {
        printf("Enter element %d: ", i+1);
        scanf("%d", &arr[i]);
    }
    
    // Display values
    printf("\nArray elements: ");
    for(i = 0; i < n; i++) {
        printf("%d ", arr[i]);
    }
    
    // Free memory
    free(arr);
    arr = NULL;
    
    return 0;
}

Problem 2: Dynamic 2D Array

Problem:

Allocate a 2D array dynamically (matrix) and properly deallocate it.

Solution:

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

int main() {
    int rows = 3, cols = 4, i, j;
    int **matrix;
    
    // Allocate array of row pointers
    matrix = (int**) malloc(rows * sizeof(int*));
    
    // Allocate each row
    for(i = 0; i < rows; i++) {
        matrix[i] = (int*) malloc(cols * sizeof(int));
    }
    
    // Use the matrix...
    for(i = 0; i < rows; i++) {
        for(j = 0; j < cols; j++) {
            matrix[i][j] = i * cols + j;
        }
    }
    
    // IMPORTANT: Free in reverse order!
    for(i = 0; i < rows; i++) {
        free(matrix[i]); // Free each row first
    }
    free(matrix); // Then free the row pointer array
    
    return 0;
}

Problem 3: Dynamic String Handling

Problem:

Create a function that dynamically allocates memory for a string, copies user input, and returns it. The caller is responsible for freeing the memory.

Solution:

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

char* getString() {
    char temp[100];
    char *str;
    
    printf("Enter a string: ");
    fgets(temp, 100, stdin);
    
    // Allocate exact size needed
    str = (char*) malloc(strlen(temp) + 1);
    
    if(str == NULL) {
        return NULL;
    }
    
    strcpy(str, temp);
    return str;
}

int main() {
    char *myString = getString();
    
    if(myString != NULL) {
        printf("You entered: %s", myString);
        free(myString); // Caller must free!
        myString = NULL;

Problem 4: Resizing Array with realloc

Problem:

Start with an array of 5 integers, then dynamically resize it to hold 10 integers without losing existing data.

Solution:

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

int main() {
    int *arr;
    int i;
    
    // Initial allocation
    arr = (int*) malloc(5 * sizeof(int));
    
    // Fill initial array
    for(i = 0; i < 5; i++) {
        arr[i] = i + 1;
    }
    
    printf("Initial array: ");
    for(i = 0; i < 5; i++) {
        printf("%d ", arr[i]);
    }
    
    // Resize to 10 elements
    arr = (int*) realloc(arr, 10 * sizeof(int));
    
    if(arr == NULL) {
        printf("Reallocation failed!\n");
        return -1;
    }
    
    // Fill new elements
    for(i = 5; i < 10; i++) {
        arr[i] = i + 1;
    }
    
    printf("\nResized array: ");
    for(i = 0; i < 10; i++) {
        printf("%d ", arr[i]);
    }
    
    free(arr);
    return 0;

Problem 5: Linked List Memory Management

Problem:

Create a simple linked list, add nodes dynamically, and properly free all nodes.

Solution:

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

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

struct Node* createNode(int data) {
    struct Node *newNode = (struct Node*) malloc(sizeof(struct Node));
    if(newNode == NULL) {
        printf("Memory allocation failed!\n");
        return NULL;
    }
    newNode->data = data;
    newNode->next = NULL;
    return newNode;
}

void freeList(struct Node *head) {
    struct Node *temp;
    while(head != NULL) {
        temp = head;
        head = head->next;
        free(temp); // Free each node
    }
}

int main() {
    struct Node *head = createNode(10);
    head->next = createNode(20);
    head->next->next = createNode(30);
    
    // Use the list...
    struct Node *temp = head;
    printf("List: ");
    while(temp != NULL) {
        printf("%d -> ", temp->data);
        temp = temp->next;
    }
    printf("NULL\n");
    
    // Free all nodes
    freeList(head);
    
    return 0;

Common Memory Management Mistakes

Top Mistakes to Avoid

1. Forgetting to Free Memory

void badFunction() { int *ptr = malloc(100 * sizeof(int)); // Use ptr... // Forgot to free! Memory leak! }

2. Using Freed Memory

int *ptr = malloc(sizeof(int)); free(ptr); *ptr = 10; // ERROR: Using freed memory!

3. Not Checking malloc Return Value

int *ptr = malloc(sizeof(int)); *ptr = 5; // What if malloc failed? Crash!

4. Freeing Stack Memory

int x = 10; int *ptr = &x; free(ptr); // ERROR: Can't free stack memory!

Problem 6: Student Database

Problem:

Create a dynamic array of student structures. Allow user to input student data and properly manage memory.

Solution:

#include <stdio.h> #include <stdlib.h> #include <string.h> struct Student { char name[50]; int rollNo; float marks; }; int main() { int n, i; struct Student *students; printf("Enter number of students: "); scanf("%d", &n); // Allocate memory for n students students = (struct Student*) malloc(n * sizeof(struct Student)); if(students == NULL) { printf("Memory allocation failed!\n"); return -1; } // Input student data for(i = 0; i < n; i++) { printf("\nStudent %d:\n", i+1); printf("Name: "); scanf("%s", students[i].name); printf("Roll No: "); scanf("%d", &students[i].rollNo); printf("Marks: "); scanf("%f", &students[i].marks); } // Display student data printf("\n\nStudent Records:\n"); for(i = 0; i < n; i++) { printf("%s\t%d\t%.2f\n", students[i].name, students[i].rollNo, students[i].marks); } // Free memory free(students); students = NULL; return 0; }

Memory Debugging Tools

Tools to Detect Memory Issues

1. Valgrind (Linux/Mac)

Usage: valgrind --leak-check=full ./program

Detects: Memory leaks, invalid memory access, use after free

2. Address Sanitizer (ASan)

Compilation: gcc -fsanitize=address program.c

Detects: Buffer overflows, use after free, memory leaks

3. Dr. Memory (Windows)

Free tool for Windows that detects memory errors including:

  • Memory leaks
  • Use after free
  • Buffer overflows
  • Uninitialized memory access

4. Manual Techniques

  • Print statements before/after malloc and free
  • Keep track of allocations in a separate log
  • Use assert() to verify assumptions
  • Set pointers to NULL after freeing

Problem 7: Find and Fix Memory Issues

Problem:

Identify and fix ALL memory issues in the following code:

#include <stdio.h> #include <stdlib.h> int main() { int *arr1 = malloc(5 * sizeof(int)); int *arr2 = malloc(3 * sizeof(int)); arr1[0] = 10; free(arr1); printf("%d", arr1[0]); free(arr2); free(arr2); return 0; }

Issues Found:

  1. Missing cast for malloc
  2. Not checking if malloc succeeded
  3. Using arr1 after freeing (dangling pointer)
  4. Double free on arr2
  5. Not setting pointers to NULL after free

Corrected Code:

#include <stdio.h> #include <stdlib.h> int main() { int *arr1 = (int*)malloc(5 * sizeof(int)); int *arr2 = (int*)malloc(3 * sizeof(int)); // Check allocations if(arr1 == NULL || arr2 == NULL) { printf("Allocation failed!\n"); return -1; } arr1[0] = 10; printf("%d", arr1[0]); // Use before free free(arr1); arr1 = NULL; free(arr2); arr2 = NULL; // Prevents double free return 0; }

Memory Layout of a C Program

Understanding Where Memory is Allocated

Memory Segment Contains Management
Stack Local variables, function parameters, return addresses Automatic (LIFO)
Heap Dynamically allocated memory (malloc, calloc) Manual (programmer)
Data Segment Global & static variables (initialized) Automatic
BSS Segment Uninitialized global & static variables Automatic
Text Segment Program code (instructions) Read-only
Key Point: Only heap memory requires manual management using malloc/calloc/realloc/free!

Problem 8: Dynamic String Array

Problem:

Create a program that reads n strings from user, stores them dynamically, sorts them, and displays them. Properly free all memory.

Solution (Part 1):

#include <stdio.h> #include <stdlib.h> #include <string.h> int main() { int n, i, j; char **strings; char temp[100], *tempPtr; printf("Enter number of strings: "); scanf("%d", &n); getchar(); // Consume newline // Allocate array of string pointers strings = (char**) malloc(n * sizeof(char*)); if(strings == NULL) { printf("Memory allocation failed!\n"); return -1; } // Read strings for(i = 0; i < n; i++) { printf("Enter string %d: ", i+1); fgets(temp, 100, stdin); temp[strcspn(temp, "\n")] = 0; // Remove newline // Allocate exact memory for string strings[i] = (char*) malloc((strlen(temp) + 1) * sizeof(char)); strcpy(strings[i], temp); }

Problem 8: Solution (Part 2)

// Bubble sort strings for(i = 0; i < n-1; i++) { for(j = 0; j < n-i-1; j++) { if(strcmp(strings[j], strings[j+1]) > 0) { // Swap pointers tempPtr = strings[j]; strings[j] = strings[j+1]; strings[j+1] = tempPtr; } } } // Display sorted strings printf("\nSorted strings:\n"); for(i = 0; i < n; i++) { printf("%d. %s\n", i+1, strings[i]); } // Free memory - each string first, then array for(i = 0; i < n; i++) { free(strings[i]); } free(strings); return 0; }

Tips for Exams & Practical

Memory Management Checklist

✓ Before Writing Code:

  • Plan which data needs dynamic allocation
  • Calculate exact memory needed
  • Identify when memory should be freed
  • Design clear ownership of memory

✓ While Writing Code:

  • Always cast malloc/calloc return value
  • Check if allocation succeeded (NULL check)
  • Use sizeof() instead of hardcoded sizes
  • Match every malloc with a free
  • Set pointer to NULL after freeing

✓ After Writing Code:

  • Count malloc calls = free calls?
  • Check for memory leaks in all code paths
  • Verify no use-after-free
  • Test with different inputs
  • Use tools like Valgrind/ASan

✓ Common Mistakes to Avoid:

  • Forgetting to free memory
  • Using memory after freeing it
  • Not checking for allocation failures
  • Memory leaks in error paths
  • Buffer overflows/underflows

Summary

Key Takeaways

What We Learned:

  1. Garbage Collection: Automatic memory management (not in C)
  2. Memory Leaks: Allocated memory not freed
  3. Dangling Pointers: Pointers to freed memory
  4. Double Free: Freeing same memory twice
  5. Best Practices: Check malloc, free properly, set NULL

Memory Management is Critical For:

  • Preventing crashes and bugs
  • Efficient resource usage
  • Professional-quality code
  • System stability
  • Security & performance

The Golden Rule of Memory Management:

Every malloc() must have a corresponding free()

Remember to:

  • Check for NULL after allocation
  • Free in the reverse order of allocation
  • Set pointers to NULL after freeing
  • Use tools to verify memory safety

Practice Questions for Students

Conceptual Questions:

  1. Why doesn't C have automatic garbage collection?
  2. What is the difference between a memory leak and a dangling pointer?
  3. When should you use malloc vs calloc?
  4. What happens if you free() the same pointer twice?

Practical Assignments:

  1. Write a program to implement a dynamic stack using linked list
  2. Create a phone book application using dynamic arrays
  3. Implement a function to concatenate two dynamic strings
  4. Build a simple memory leak detector that tracks allocations
Tip: Practice these problems and always run your code through valgrind or similar tools to check for memory issues!

Thank You!

Questions?

Mohsin F. Dar

Assistant Professor

Cloud & Software Operations Cluster | SOCS

UPES

"Good programmers manage memory.
Great programmers manage it perfectly."

1 / 24