Day 10: Dynamic Memory Allocation#

Overview#

So far, we’ve allocated memory at compile time (fixed-size arrays). Today we’ll learn how to allocate memory at runtime using malloc, calloc, realloc, and free. This is crucial for writing flexible programs.

What We’ll Learn Today#

  • Why dynamic memory allocation
  • malloc function
  • calloc function
  • realloc function
  • free function
  • Memory management best practices
  • Avoiding memory leaks
  • Common memory errors

Why Dynamic Memory Allocation?#

Problem with Static Arrays#

int arr[100];  // Always allocates 100 integers
// What if we only need 10? Wasteful!
// What if we need 1000? Too small!

Solution: Dynamic Allocation#

int n = 10;  // Determined at runtime
int* arr = malloc(n * sizeof(int));  // Allocate exactly what we need

The malloc() Function#

Allocates a block of memory and returns a pointer:

void* malloc(size_t size);
  • size: Number of bytes to allocate
  • Returns: Pointer to allocated memory (or NULL if failed)

Basic Usage#

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

int main() {
    // Allocate memory for 5 integers
    int* ptr = malloc(5 * sizeof(int));

    if (ptr == NULL) {
        printf("Memory allocation failed!\n");
        return 1;
    }

    // Use the memory
    ptr[0] = 10;
    ptr[1] = 20;
    ptr[2] = 30;
    ptr[3] = 40;
    ptr[4] = 50;

    printf("Values: %d %d %d\n", ptr[0], ptr[1], ptr[2]);

    // Free the memory when done
    free(ptr);

    return 0;
}

Why Check for NULL?#

int* ptr = malloc(size);

// ✅ Always check
if (ptr == NULL) {
    printf("Memory allocation failed!\n");
    return 1;
}

Allocation can fail if system runs out of memory!


Why sizeof()?#

Always use sizeof() to make code portable:

// ❌ Not portable - assumes int is 4 bytes
int* ptr = malloc(5 * 4);

// ✅ Portable - works on any system
int* ptr = malloc(5 * sizeof(int));

// For floats
float* fptr = malloc(10 * sizeof(float));

// For arrays of structures
typedef struct {
    int id;
    char name[50];
} Student;
Student* students = malloc(20 * sizeof(Student));

The calloc() Function#

Allocates memory AND initializes all bytes to zero:

void* calloc(size_t num, size_t size);
  • num: Number of elements
  • size: Size of each element
  • Returns: Pointer (or NULL if failed)

calloc vs malloc#

// malloc - uninitialized (garbage values)
int* arr1 = malloc(5 * sizeof(int));
printf("%d\n", arr1[0]);  // Garbage value

// calloc - initialized to zero
int* arr2 = calloc(5, sizeof(int));
printf("%d\n", arr2[0]);  // 0

free(arr1);
free(arr2);

Example#

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

int main() {
    int n = 5;

    // Allocate array of integers, all initialized to 0
    int* arr = calloc(n, sizeof(int));

    if (arr == NULL) {
        printf("Allocation failed!\n");
        return 1;
    }

    // Print values (all are 0)
    for (int i = 0; i < n; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");  // Output: 0 0 0 0 0

    free(arr);
    return 0;
}

The realloc() Function#

Resizes previously allocated memory:

void* realloc(void* ptr, size_t new_size);
  • ptr: Previous pointer from malloc/calloc
  • new_size: New size in bytes
  • Returns: Pointer to resized memory (may be new address)

Example: Growing an Array#

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

int main() {
    // Start with 3 integers
    int* arr = malloc(3 * sizeof(int));

    if (arr == NULL) {
        printf("First allocation failed!\n");
        return 1;
    }

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

    printf("First allocation: ");
    for (int i = 0; i < 3; i++) printf("%d ", arr[i]);
    printf("\n");

    // Resize to 5 integers
    arr = realloc(arr, 5 * sizeof(int));

    if (arr == NULL) {
        printf("Reallocation failed!\n");
        return 1;
    }

    // Initialize new elements
    arr[3] = 40;
    arr[4] = 50;

    printf("After realloc: ");
    for (int i = 0; i < 5; i++) printf("%d ", arr[i]);
    printf("\n");

    free(arr);
    return 0;
}

Output:

First allocation: 10 20 30
After realloc: 10 20 30 40 50

The free() Function#

Deallocate memory to prevent memory leaks:

free(ptr);
ptr = NULL;  // Good practice

Important: Set to NULL After Freeing#

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

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

    free(ptr);
    ptr = NULL;  // Prevent use-after-free

    // ❌ Don't do this after freeing
    // printf("%d\n", *ptr);  // Undefined behavior!

    return 0;
}

Memory Leaks#

A memory leak occurs when allocated memory is never freed:

Example of Memory Leak#

void leaky_function() {
    int* ptr = malloc(100 * sizeof(int));

    // ... use ptr ...

    return;  // ❌ Memory not freed! Leak!
}

int main() {
    leaky_function();
    leaky_function();
    // Multiple leaks accumulate
    return 0;
}

Fixed Version#

void correct_function() {
    int* ptr = malloc(100 * sizeof(int));

    // ... use ptr ...

    free(ptr);  // ✅ Memory freed
    return;
}

Common Leak Pattern#

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

    if (error_condition) {
        printf("Error!\n");
        return 1;  // ❌ Memory not freed!
    }

    free(ptr);
    return 0;
}

Fixed:

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

    if (error_condition) {
        printf("Error!\n");
        status = 1;
    }

    free(ptr);
    return status;
}

Practical Example: Dynamic Array#

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

int main() {
    int n;

    printf("How many numbers? ");
    scanf("%d", &n);

    // Allocate array
    int* arr = malloc(n * sizeof(int));

    if (arr == NULL) {
        printf("Memory allocation failed!\n");
        return 1;
    }

    // Input
    printf("Enter %d numbers: ", n);
    for (int i = 0; i < n; i++) {
        scanf("%d", &arr[i]);
    }

    // Calculate sum
    int sum = 0;
    for (int i = 0; i < n; i++) {
        sum += arr[i];
    }

    // Calculate average
    float average = sum / (float)n;

    printf("Sum: %d\n", sum);
    printf("Average: %.2f\n", average);

    // Free memory
    free(arr);

    return 0;
}

Dynamic Array of Structures#

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

typedef struct {
    int id;
    char name[50];
    float salary;
} Employee;

int main() {
    int count;

    printf("How many employees? ");
    scanf("%d", &count);

    // Allocate array of Employee structures
    Employee* emp = malloc(count * sizeof(Employee));

    if (emp == NULL) {
        printf("Memory allocation failed!\n");
        return 1;
    }

    // Input
    for (int i = 0; i < count; i++) {
        printf("\nEmployee %d:\n", i + 1);

        printf("ID: ");
        scanf("%d", &emp[i].id);

        printf("Name: ");
        scanf("%s", emp[i].name);

        printf("Salary: ");
        scanf("%f", &emp[i].salary);
    }

    // Display
    printf("\n=== Employee Records ===\n");
    float total_salary = 0;
    for (int i = 0; i < count; i++) {
        printf("ID: %d, Name: %s, Salary: $%.2f\n",
               emp[i].id, emp[i].name, emp[i].salary);
        total_salary += emp[i].salary;
    }

    printf("Total Salary: $%.2f\n", total_salary);

    // Free memory
    free(emp);

    return 0;
}

Memory Management Best Practices#

1. Always Check for NULL#

ptr = malloc(size);
if (ptr == NULL) {
    printf("Allocation failed!\n");
    return -1;
}

2. Free All Allocated Memory#

int* arr = malloc(n * sizeof(int));
// ... use arr ...
free(arr);

3. Set Pointer to NULL After Freeing#

free(ptr);
ptr = NULL;

4. Avoid Double Free#

free(ptr);
free(ptr);  // ❌ Double free error!

5. Don’t Free Stack Variables#

int x = 10;
free(&x);  // ❌ Wrong! x is on stack

6. Use sizeof() Consistently#

int* arr = malloc(n * sizeof(int));      // ✅ Correct
// NOT: malloc(n * 4);                   // ❌ Not portable

Common Errors#

ErrorExampleFix
Memory leakmalloc() without free()Always free()
Null pointer dereference*NULLCheck for NULL
Double freefree() twiceDon’t free twice
Buffer overflowWriting beyond allocatedCheck bounds
Use-after-freeUse pointer after free()Set to NULL

Practice Exercises#

Exercise 1: Resizable Array#

Write a program that:

  • Takes numbers until user enters -1
  • Dynamically grows array as needed (use realloc)
  • Displays all numbers
  • Frees memory

Exercise 2: Matrix Operations#

Write a program that:

  • Dynamically allocates 2D array (array of pointers)
  • Inputs matrix values
  • Calculates sum and average
  • Frees all memory

Exercise 3: String Duplication#

Write a function that:

  • Takes a string
  • Allocates memory
  • Copies string to new location
  • Returns pointer
  • Main function frees memory

Summary#

✅ Understood why dynamic allocation is needed
✅ Used malloc() for allocation
✅ Used calloc() for zero-initialized memory
✅ Used realloc() to resize memory
✅ Used free() to deallocate memory
✅ Avoided memory leaks
✅ Handled common memory errors

Key Points to Remember#

  1. malloc() - allocate uninitialized memory
  2. calloc() - allocate zero-initialized memory
  3. realloc() - resize existing allocation
  4. free() - deallocate memory
  5. Always check for NULL after allocation
  6. Use sizeof() for portability
  7. Set pointer to NULL after freeing
  8. Every malloc()/calloc()/realloc() needs a free()

Next Steps#

Tomorrow we’ll learn about recursion - a powerful programming technique!

→ Continue to Day 11

← Back to Day 9