Docs

Dynamic Memory Allocation

Dynamic Memory Allocation

Table of Contents

  1. •Introduction
  2. •Stack vs Heap Memory
  3. •malloc Function
  4. •calloc Function
  5. •realloc Function
  6. •free Function
  7. •Memory Allocation Best Practices
  8. •Common Memory Errors
  9. •Dynamic Arrays
  10. •Dynamic 2D Arrays
  11. •Dynamic Structures
  12. •Memory Debugging Tools
  13. •Summary

Introduction

Dynamic Memory Allocation allows programs to request memory at runtime from the heap. Unlike stack memory (fixed at compile time), heap memory can be:

  • •Allocated when needed
  • •Resized during execution
  • •Freed when no longer required

Why Use Dynamic Memory?

Use CaseReason
Unknown size at compile timeUser input determines array size
Large data structuresStack has limited size
Data persistenceSurvives function scope
Flexible data structuresLinked lists, trees, graphs
Efficient memory useAllocate only what's needed

Memory Allocation Functions

FunctionPurposeHeader
malloc()Allocate uninitialized memory<stdlib.h>
calloc()Allocate zero-initialized memory<stdlib.h>
realloc()Resize previously allocated memory<stdlib.h>
free()Release allocated memory<stdlib.h>

Stack vs Heap Memory

Stack Memory

void function() {
    int x = 10;           // Stack: automatic allocation
    int arr[100];         // Stack: fixed size array
}  // Automatically freed when function returns

Characteristics:

  • •Automatic allocation/deallocation
  • •Fast allocation (just move stack pointer)
  • •Limited size (typically 1-8 MB)
  • •LIFO (Last In, First Out)
  • •Scope-limited lifetime

Heap Memory

void function() {
    int *p = malloc(sizeof(int));     // Heap: manual allocation
    int *arr = malloc(100 * sizeof(int));
    // Must be freed manually!
    free(p);
    free(arr);
}

Characteristics:

  • •Manual allocation/deallocation
  • •Slower than stack
  • •Much larger (limited by RAM)
  • •No automatic cleanup
  • •Persists until freed

Memory Layout Diagram

High Address
ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
│    Command Line      │
│      Arguments       │
ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤
│    Stack             │  ← Local variables, function calls
│    ↓ grows down      │
│                      │
│                      │
│    ↑ grows up        │
│    Heap              │  ← Dynamic memory (malloc, etc.)
ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤
│    BSS               │  ← Uninitialized global/static
ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤
│    Data              │  ← Initialized global/static
ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤
│    Text (Code)       │  ← Program instructions
ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜
Low Address

malloc Function

Syntax

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 one integer
    int *ptr = (int*)malloc(sizeof(int));

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

    *ptr = 42;
    printf("Value: %d\n", *ptr);

    free(ptr);  // Don't forget!
    return 0;
}

Allocating Arrays

// Allocate array of 10 integers
int *arr = (int*)malloc(10 * sizeof(int));

if (arr != NULL) {
    for (int i = 0; i < 10; i++) {
        arr[i] = i * 10;
    }

    // Use array...

    free(arr);
}

Important Notes

  1. •Memory is uninitialized - contains garbage values
  2. •Always check for NULL - allocation can fail
  3. •Always use sizeof() - portable across platforms
  4. •Cast is optional in C - but explicit casts improve readability
// With cast (explicit)
int *p = (int*)malloc(sizeof(int));

// Without cast (valid C, not C++)
int *p = malloc(sizeof(int));

calloc Function

Syntax

void* calloc(size_t num, size_t size);
  • •num: Number of elements
  • •size: Size of each element
  • •Returns: Pointer to zero-initialized memory, or NULL

Comparison with malloc

// Using malloc - memory contains garbage
int *arr1 = (int*)malloc(5 * sizeof(int));
// arr1[0] to arr1[4] contain random values!

// Using calloc - memory is zeroed
int *arr2 = (int*)calloc(5, sizeof(int));
// arr2[0] to arr2[4] are all 0

When to Use calloc

Use calloc when...Use malloc when...
Need zero-initialized memoryWill initialize immediately
Allocating arraysSingle element allocation
Security-sensitive dataPerformance is critical

Example

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

int main() {
    int n = 5;

    // Allocate and zero-initialize array
    int *arr = (int*)calloc(n, sizeof(int));

    if (arr == NULL) {
        return 1;
    }

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

    free(arr);
    return 0;
}

realloc Function

Syntax

void* realloc(void *ptr, size_t new_size);
  • •ptr: Pointer to previously allocated memory
  • •new_size: New size in bytes
  • •Returns: Pointer to resized memory (may be different!), or NULL

How realloc Works

  1. •If space available after current block: extends in place
  2. •Otherwise: allocates new block, copies data, frees old block
  3. •If new_size is 0: equivalent to free()
  4. •If ptr is NULL: equivalent to malloc()

Important Pattern

// WRONG - may lose original data on failure
ptr = realloc(ptr, new_size);  // If returns NULL, original is lost!

// CORRECT - preserve original on failure
int *temp = realloc(ptr, new_size);
if (temp != NULL) {
    ptr = temp;  // Update only on success
} else {
    // Handle error, original ptr still valid
}

Growing an Array

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

int main() {
    int capacity = 2;
    int size = 0;

    int *arr = (int*)malloc(capacity * sizeof(int));
    if (arr == NULL) return 1;

    // Add elements, grow as needed
    int values[] = {10, 20, 30, 40, 50};

    for (int i = 0; i < 5; i++) {
        if (size >= capacity) {
            // Double the capacity
            capacity *= 2;
            int *temp = (int*)realloc(arr, capacity * sizeof(int));
            if (temp == NULL) {
                free(arr);
                return 1;
            }
            arr = temp;
            printf("Grew to capacity %d\n", capacity);
        }
        arr[size++] = values[i];
    }

    printf("Final array: ");
    for (int i = 0; i < size; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");

    free(arr);
    return 0;
}

Shrinking Memory

// Shrink array from 100 to 50 elements
int *arr = (int*)malloc(100 * sizeof(int));
// ... use 100 elements ...

// Now we only need 50
int *temp = (int*)realloc(arr, 50 * sizeof(int));
if (temp != NULL) {
    arr = temp;  // Shrank successfully
}

free Function

Syntax

void free(void *ptr);

Rules for free()

  1. •

    Only free dynamically allocated memory

    int x = 10;
    free(&x);  // WRONG! x is on stack
    
  2. •

    Don't free twice (double free)

    int *p = malloc(sizeof(int));
    free(p);
    free(p);  // WRONG! Undefined behavior
    
  3. •

    Set pointer to NULL after freeing

    int *p = malloc(sizeof(int));
    free(p);
    p = NULL;  // Good practice
    
  4. •

    free(NULL) is safe

    int *p = NULL;
    free(p);  // OK, does nothing
    

Memory Leak Prevention

// Memory leak - allocated but never freed
void leak() {
    int *p = malloc(100 * sizeof(int));
    // Function returns without free()
}

// Correct
void noLeak() {
    int *p = malloc(100 * sizeof(int));
    // ... use p ...
    free(p);
}

Memory Allocation Best Practices

1. Always Check Return Value

int *arr = (int*)malloc(n * sizeof(int));
if (arr == NULL) {
    fprintf(stderr, "Memory allocation failed\n");
    exit(EXIT_FAILURE);
}

2. Use sizeof with Variable, Not Type

// Good - automatically correct if type changes
int *arr = malloc(n * sizeof(*arr));

// Less safe - must update if type changes
int *arr = malloc(n * sizeof(int));

3. Free Memory in Reverse Order of Allocation

// Allocate
char *name = malloc(50);
int *data = malloc(100 * sizeof(int));

// Free in reverse order
free(data);
free(name);

4. Use Wrapper Functions

void* safe_malloc(size_t size) {
    void *ptr = malloc(size);
    if (ptr == NULL && size != 0) {
        fprintf(stderr, "Memory allocation failed\n");
        exit(EXIT_FAILURE);
    }
    return ptr;
}

5. Initialize Pointers

int *ptr = NULL;  // Good: initialize to NULL

// Later, can safely check
if (ptr == NULL) {
    ptr = malloc(sizeof(int));
}

6. Document Ownership

/**
 * Creates a new string buffer.
 * @param size Buffer size
 * @return Newly allocated buffer. Caller must free().
 */
char* createBuffer(size_t size);

Common Memory Errors

1. Memory Leak

void leak() {
    int *p = malloc(1000 * sizeof(int));
    // Forgot to free!
}  // Memory is lost

Fix: Always pair malloc with free.

2. Dangling Pointer

int *p = malloc(sizeof(int));
*p = 42;
free(p);
printf("%d\n", *p);  // WRONG! p is dangling

Fix: Set pointer to NULL after free.

3. Double Free

int *p = malloc(sizeof(int));
free(p);
free(p);  // CRASH or corruption!

Fix: Set to NULL after free; check before free.

4. Buffer Overflow

int *arr = malloc(5 * sizeof(int));
for (int i = 0; i <= 5; i++) {  // Off by one!
    arr[i] = i;  // arr[5] is out of bounds
}

Fix: Use correct bounds checking.

5. Use After Free

int *p = malloc(sizeof(int));
*p = 10;
free(p);
*p = 20;  // WRONG! Memory already freed

Fix: Don't access freed memory.

6. Wrong Size

int *arr = malloc(10);  // WRONG! Only 10 bytes, not 10 ints
int *arr = malloc(10 * sizeof(int));  // CORRECT

7. Freeing Stack Memory

int arr[10];
free(arr);  // CRASH! arr is on stack

Fix: Only free heap memory.


Dynamic Arrays

Creating Dynamic Arrays

int n = 10;
int *arr = (int*)malloc(n * sizeof(int));

// Use like normal array
for (int i = 0; i < n; i++) {
    arr[i] = i * 2;
}

free(arr);

Resizable Array (Vector)

typedef struct {
    int *data;
    int size;
    int capacity;
} Vector;

Vector* vector_create() {
    Vector *v = (Vector*)malloc(sizeof(Vector));
    v->data = (int*)malloc(4 * sizeof(int));
    v->size = 0;
    v->capacity = 4;
    return v;
}

void vector_push(Vector *v, int value) {
    if (v->size >= v->capacity) {
        v->capacity *= 2;
        v->data = (int*)realloc(v->data, v->capacity * sizeof(int));
    }
    v->data[v->size++] = value;
}

void vector_free(Vector *v) {
    free(v->data);
    free(v);
}

Dynamic 2D Arrays

Method 1: Array of Pointers

int rows = 3, cols = 4;

// Allocate row pointers
int **matrix = (int**)malloc(rows * sizeof(int*));

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

// Use
matrix[1][2] = 42;

// Free (in reverse order)
for (int i = 0; i < rows; i++) {
    free(matrix[i]);
}
free(matrix);

Method 2: Contiguous Allocation

int rows = 3, cols = 4;

// Allocate all data contiguously
int *data = (int*)malloc(rows * cols * sizeof(int));
int **matrix = (int**)malloc(rows * sizeof(int*));

// Set up row pointers
for (int i = 0; i < rows; i++) {
    matrix[i] = data + (i * cols);
}

// Use same way
matrix[1][2] = 42;

// Simpler free
free(data);
free(matrix);

Comparison

AspectArray of PointersContiguous
Allocation callsrows + 12
Memory overheadMore pointersLess
Cache localityPoorExcellent
Ragged arraysPossibleNo
Free callsrows + 12

Dynamic Structures

Allocating Structures

typedef struct {
    char name[50];
    int age;
} Person;

// Single structure
Person *p = (Person*)malloc(sizeof(Person));
strcpy(p->name, "Alice");
p->age = 30;

// Array of structures
Person *people = (Person*)malloc(10 * sizeof(Person));
strcpy(people[0].name, "Bob");
people[0].age = 25;

free(p);
free(people);

Structures with Dynamic Members

typedef struct {
    char *name;    // Dynamically allocated
    int *scores;   // Dynamically allocated array
    int num_scores;
} Student;

Student* create_student(const char *name, int num_scores) {
    Student *s = (Student*)malloc(sizeof(Student));
    s->name = (char*)malloc(strlen(name) + 1);
    strcpy(s->name, name);
    s->scores = (int*)calloc(num_scores, sizeof(int));
    s->num_scores = num_scores;
    return s;
}

void free_student(Student *s) {
    free(s->name);    // Free name first
    free(s->scores);  // Free scores
    free(s);          // Free structure last
}

Linked List Node

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

Node* create_node(int data) {
    Node *n = (Node*)malloc(sizeof(Node));
    n->data = data;
    n->next = NULL;
    return n;
}

void free_list(Node *head) {
    while (head != NULL) {
        Node *next = head->next;
        free(head);
        head = next;
    }
}

Memory Debugging Tools

Valgrind (Linux)

# Compile with debug symbols
gcc -g -o program program.c

# Run with Valgrind
valgrind --leak-check=full ./program

Output:

==12345== HEAP SUMMARY:
==12345==     in use at exit: 0 bytes in 0 blocks
==12345==   total heap usage: 5 allocs, 5 frees, 1,024 bytes allocated
==12345==
==12345== All heap blocks were freed -- no leaks are possible

AddressSanitizer (GCC/Clang)

# Compile with sanitizer
gcc -fsanitize=address -g -o program program.c

# Run - will detect memory errors
./program

Simple Tracking Wrapper

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

static int alloc_count = 0;

void* tracked_malloc(size_t size) {
    void *ptr = malloc(size);
    if (ptr) {
        alloc_count++;
        printf("ALLOC: %zu bytes at %p (total: %d)\n",
               size, ptr, alloc_count);
    }
    return ptr;
}

void tracked_free(void *ptr) {
    if (ptr) {
        alloc_count--;
        printf("FREE: %p (remaining: %d)\n", ptr, alloc_count);
        free(ptr);
    }
}

Summary

Function Quick Reference

FunctionPurposeReturnsNotes
malloc(size)Allocate bytesvoid* or NULLUninitialized
calloc(n, size)Allocate n elementsvoid* or NULLZero-initialized
realloc(ptr, size)Resize allocationvoid* or NULLPreserves data
free(ptr)Release memoryvoidSafe with NULL

Memory Management Checklist

  • • Always check malloc/calloc/realloc return value
  • • Use sizeof() with the variable, not the type
  • • Free memory when done
  • • Set pointer to NULL after freeing
  • • Never double-free
  • • Never access freed memory
  • • Free in reverse order of allocation
  • • Handle realloc failure without losing original pointer

Common Patterns

// Safe allocation
int *arr = malloc(n * sizeof(*arr));
if (arr == NULL) {
    // Handle error
}

// Safe realloc
int *temp = realloc(arr, new_size * sizeof(*arr));
if (temp == NULL) {
    // Handle error, arr still valid
} else {
    arr = temp;
}

// Safe free
free(arr);
arr = NULL;

Practice Exercises

  1. •Implement a dynamic string that can grow automatically
  2. •Create a matrix multiplication function with dynamic allocation
  3. •Build a simple memory pool allocator
  4. •Implement a dynamic stack data structure
  5. •Create a program that reads unknown number of inputs from user

See the exercises.c file for hands-on practice problems with solutions.

Dynamic Memory Allocation - C Programming Tutorial | DeepML