</>
Skip to content
C lessons (26/28)

C — Memory Management

Stack vs heap

FeatureStackHeap
AllocationAutomaticManual (malloc/free)
SpeedFastSlower
SizeLimited (usually 1-8 MB)Limited by RAM
LifetimeScope-basedUntil free()

malloc

Allocate a block of uninitialized memory:

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

int main() {
    // Allocate memory for one int
    int *p = malloc(sizeof(int));
    if (p == NULL) {
        fprintf(stderr, "malloc failed\n");
        return 1;
    }
    *p = 42;
    printf("Value: %d\n", *p);
    free(p);

    // Allocate array
    int n = 5;
    int *arr = malloc(n * sizeof(int));
    if (arr == NULL) {
        fprintf(stderr, "malloc failed\n");
        return 1;
    }

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

    for (int i = 0; i < n; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n"); // 0 10 20 30 40

    free(arr);
    return 0;
}

calloc

Allocate and zero-initialize memory:

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

int main() {
    // calloc: allocates AND zeros the memory
    int *arr = calloc(5, sizeof(int));
    if (arr == NULL) {
        fprintf(stderr, "calloc failed\n");
        return 1;
    }

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

    free(arr);
    return 0;
}

Key difference: malloc leaves memory uninitialized (garbage values), calloc sets everything to zero.

realloc

Resize a previously allocated block:

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

int main() {
    int capacity = 2;
    int size = 0;
    int *arr = malloc(capacity * sizeof(int));
    if (arr == NULL) return 1;

    // Simulate dynamic growth
    for (int i = 0; i < 10; i++) {
        if (size >= capacity) {
            capacity *= 2;
            int *temp = realloc(arr, capacity * sizeof(int));
            if (temp == NULL) {
                fprintf(stderr, "realloc failed\n");
                free(arr);
                return 1;
            }
            arr = temp;
            printf("Resized to %d\n", capacity);
        }
        arr[size++] = i;
    }

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

    free(arr);
    return 0;
}

free

Release allocated memory:

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

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

    free(p);
    p = NULL; // Good practice: prevent dangling pointer

    // Don't use p after free!
    // printf("%d\n", *p); // Undefined behavior

    // Don't double-free!
    // free(p); // Undefined behavior

    return 0;
}

Common memory errors

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

int main() {
    // MEMORY LEAK: forgetting to free
    int *leak = malloc(sizeof(int));
    *leak = 42;
    // leak is never freed — memory is leaked
    // Fix: free(leak);

    // DANGLING POINTER: using after free
    int *ptr = malloc(sizeof(int));
    *ptr = 100;
    free(ptr);
    // *ptr = 200; // Undefined behavior!
    ptr = NULL;   // Safe: prevents accidental use

    // DOUBLE FREE: freeing twice
    int *dup = malloc(sizeof(int));
    free(dup);
    // free(dup); // Undefined behavior!
    dup = NULL;

    // BUFFER OVERFLOW: writing beyond allocated size
    int *small = malloc(3 * sizeof(int));
    // small[5] = 99; // Undefined behavior!
    free(small);

    printf("Memory management patterns demonstrated\n");
    return 0;
}

Dynamic 2D arrays

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

int main() {
    int rows = 3, cols = 4;

    // Allocate rows
    int **matrix = malloc(rows * sizeof(int *));
    if (matrix == NULL) return 1;

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

    // Fill
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            matrix[i][j] = i * cols + j;
        }
    }

    // Print
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            printf("%3d", matrix[i][j]);
        }
        printf("\n");
    }

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

    return 0;
}

Memory debugging tools

ToolPurpose
valgrindDetect memory leaks and errors
AddressSanitizerCompile-time memory error detection
-Wall -WextraCompiler warnings
# Compile with AddressSanitizer
gcc -fsanitize=address -g program.c -o program

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

Best practices

  • Always check if malloc/calloc/realloc returned NULL
  • Always free what you malloc
  • Set pointers to NULL after freeing
  • Never use memory after freeing it
  • Match allocation and deallocation functions
  • Use sizeof for portability, not hardcoded sizes

Mini Practice

Write C code that:

  1. Dynamically allocates an array of 10 integers
  2. Uses realloc to grow it to 20 elements
  3. Creates a dynamic 2D array and frees it properly
  4. Demonstrates a memory leak and how to fix it

Up Next

Congratulations! You've completed the C fundamentals. Continue exploring advanced topics like data structures, algorithms, and system programming.

Related Topics

Frequently Asked Questions about Memory Management

What is Memory Management in C?

Memory Management is a fundamental concept in C. This lesson explains it step by step with clear examples, making it easy for beginners to understand.

How do I learn Memory Management?

Start by reading the explanation above, then try the code examples. Practice by modifying the examples and experimenting with different values. Hands-on practice is the best way to learn Memory Management.

Why is Memory Management important in C?

Memory Management is essential for C development. Understanding this concept will help you write better code and solve real-world problems more effectively.