Skip to content

C Dynamic Memory — Malloc, Calloc, Realloc, and Free Explained

DodaTech Updated 2026-06-28 8 min read

In this tutorial, you will learn about C Dynamic Memory. We cover key concepts, practical examples, and best practices to help you master this topic.

C dynamic memory allocation uses malloc, calloc, realloc, and free to manage heap memory at runtime, enabling programs to handle data of unknown size and create data structures that persist beyond function scope.

Why It Matters

Static and stack allocation are not enough for real programs. You cannot know at compile time how many items a user will enter, how large a file will be, or how many connections a server must handle. Dynamic memory lets you allocate exactly what you need at runtime. Mismanaging dynamic memory leads to leaks, crashes, and security vulnerabilities.

Real-World Use

Every non-trivial C program uses dynamic memory. Web servers allocate connection structs dynamically. Databases allocate cache buffers. Image editors allocate pixel buffers sized by the image dimensions. Durga Antivirus Pro allocates signature databases dynamically as new threats are detected.

What You Will Learn

  • Allocating memory with malloc
  • Zero-initialized allocation with calloc
  • Resizing allocations with realloc
  • Deallocating memory with free
  • Common memory management patterns
  • Debugging memory errors

Learning Path

flowchart LR
  A[Functions & Pointers] --> B[Dynamic Memory
You are here] B --> C[Memory Layout] C --> D[Structs] D --> E[File I/O] style B fill:#f90,color:#fff

malloc -- Memory Allocation

malloc allocates a block of uninitialized memory and returns a void pointer to it:

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

int main() {
    int *arr;
    int n = 5;
    
    // Allocate memory for 5 integers
    arr = (int*)malloc(n * sizeof(int));
    
    // Always check if allocation succeeded
    if (arr == NULL) {
        printf("Memory allocation failed!\n");
        return 1;
    }
    
    // Use the memory
    for (int i = 0; i < n; i++) {
        arr[i] = i * 10;
    }
    
    printf("Array elements: ");
    for (int i = 0; i < n; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
    
    // Free the memory
    free(arr);
    
    return 0;
}

Expected output: Array elements: 0 10 20 30 40

Key Points About malloc

  • Takes the number of bytes to allocate
  • Returns void* (implicitly convertible to any pointer type in C)
  • Returns NULL if allocation fails
  • Memory content is uninitialized (contains garbage)
  • Always use sizeof(type) to calculate bytes
  • Casting the result is optional in C but recommended for clarity

calloc -- Clear Allocation

calloc allocates memory for an array of elements and zero-initializes all bytes:

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

int main() {
    int *arr;
    int n = 5;
    
    // Allocate and zero-initialize 5 integers
    arr = (int*)calloc(n, sizeof(int));
    
    if (arr == NULL) {
        printf("Memory allocation failed!\n");
        return 1;
    }
    
    // All elements are guaranteed to be 0
    for (int i = 0; i < n; i++) {
        printf("arr[%d] = %d\n", i, arr[i]);
    }
    
    free(arr);
    
    return 0;
}

Expected output:

arr[0] = 0
arr[1] = 0
arr[2] = 0
arr[3] = 0
arr[4] = 0

malloc vs calloc

Aspect malloc calloc
Arguments Number of bytes Count and element size
Initialization Uninitialized (garbage) Zero-initialized
Performance Faster (no initialization) Slower (writes zeros)
Use case When you will initialize immediately When you need zeroed memory

realloc -- Reallocation

realloc resizes an existing allocation, preserving the old content:

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

int main() {
    int *arr;
    int n = 3;
    
    // Initial allocation
    arr = (int*)malloc(n * sizeof(int));
    if (arr == NULL) return 1;
    
    arr[0] = 10;
    arr[1] = 20;
    arr[2] = 30;
    
    // Expand to hold 6 elements
    int new_n = 6;
    int *temp = (int*)realloc(arr, new_n * sizeof(int));
    
    if (temp == NULL) {
        // realloc failed, original memory is still valid
        printf("Reallocation failed!\n");
        free(arr);
        return 1;
    }
    
    arr = temp;  // Update pointer
    n = new_n;
    
    // Assign new elements
    arr[3] = 40;
    arr[4] = 50;
    arr[5] = 60;
    
    printf("Expanded array: ");
    for (int i = 0; i < n; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
    
    // Shrink
    arr = realloc(arr, 4 * sizeof(int));
    
    printf("Shrunk array: ");
    for (int i = 0; i < 4; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
    
    free(arr);
    return 0;
}

Expected output:

Expanded array: 10 20 30 40 50 60
Shrunk array: 10 20 30 40

realloc Rules

  • If the new size is larger, old content is preserved, new space is uninitialized
  • If the new size is smaller, content is truncated
  • If the pointer is NULL, realloc behaves like malloc
  • If the size is 0, realloc behaves like free
  • Returns NULL on failure, in which case the original pointer remains valid
  • Always use a temporary pointer for realloc to avoid losing the original on failure

free -- Deallocation

free releases previously allocated memory back to the heap:

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

int main() {
    int *ptr = (int*)malloc(sizeof(int));
    *ptr = 42;
    
    printf("Value: %d\n", *ptr);
    
    free(ptr);  // Release memory
    
    // ptr is now a dangling pointer!
    // *ptr = 99;  // Undefined behavior!
    
    ptr = NULL;  // Good practice: set to NULL after free
    
    // free(NULL) is safe
    free(NULL);  // Does nothing
    
    return 0;
}

Memory Management Rules

  • Every malloc/calloc/realloc must have a matching free
  • Freeing the same memory twice is undefined behavior (double-free)
  • Accessing freed memory is undefined behavior (use-after-free)
  • Setting pointer to NULL after free prevents accidental reuse
  • Forgetting to free causes memory leaks

Common Allocation Patterns

Pattern 1: Dynamic Array

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

int main() {
    int *arr = NULL;
    int count = 0;
    int capacity = 0;
    int input;
    
    printf("Enter numbers (negative to stop):\n");
    while (1) {
        scanf("%d", &input);
        if (input < 0) break;
        
        if (count >= capacity) {
            capacity = capacity ? capacity * 2 : 4;
            int *temp = realloc(arr, capacity * sizeof(int));
            if (!temp) {
                printf("Out of memory!\n");
                free(arr);
                return 1;
            }
            arr = temp;
        }
        arr[count++] = input;
    }
    
    printf("You entered: ");
    for (int i = 0; i < count; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
    
    free(arr);
    return 0;
}

Pattern 2: Allocating for a Function

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

typedef struct {
    char *name;
    int age;
} Person;

Person *create_person(const char *name, int age) {
    Person *p = malloc(sizeof(Person));
    if (!p) return NULL;
    
    p->name = malloc(strlen(name) + 1);
    if (!p->name) {
        free(p);
        return NULL;
    }
    
    strcpy(p->name, name);
    p->age = age;
    return p;
}

void destroy_person(Person *p) {
    if (p) {
        free(p->name);
        free(p);
    }
}

int main() {
    Person *alice = create_person("Alice", 30);
    if (alice) {
        printf("%s is %d years old\n", alice->name, alice->age);
        destroy_person(alice);
    }
    return 0;
}

Memory Leak Detection

Valgrind is the standard tool for detecting memory errors:

gcc -g -o program program.c
valgrind --leak-check=full ./program

Valgrind reports:

  • Memory leaks (allocated but never freed)
  • Invalid reads/writes
  • Use-after-free errors
  • Double frees

Common Mistakes

1. Forgetting to Free Memory

void leak() {
    int *p = malloc(1000);
    // p is lost when function returns -- memory leak!
}

Every allocation must have a corresponding free.

2. Using Freed Memory

int *p = malloc(sizeof(int));
free(p);
*p = 42;  // Use-after-free!

Set p to NULL after freeing.

3. Double Free

free(p);
free(p);  // Undefined behavior!

Free each pointer exactly once.

4. Not Checking malloc Return

int *p = malloc(1000000000000);  // May fail!
p[0] = 42;  // Crash if NULL

Always check for NULL after allocation.

5. Buffer Overflow on Heap

int *p = malloc(5 * sizeof(int));
p[5] = 42;  // Writing past allocated region

Heap corruption may crash later, making debugging difficult.

6. Calling realloc Without Temporary Variable

ptr = realloc(ptr, new_size);  // If realloc fails, ptr is lost!

Use a temporary pointer to avoid losing the original allocation.

Practice Questions

  1. What does malloc return if memory allocation fails? NULL. Always check the return value before using the allocated memory.

  2. What is the difference between malloc and calloc? malloc returns uninitialized memory. calloc zero-initializes the memory and takes two arguments (count and size).

  3. What should you do after calling free on a pointer? Set the pointer to NULL to prevent accidental use-after-free.

  4. What happens if you forget to free memory? The memory remains allocated until the program exits. Long-running programs will eventually exhaust available memory.

  5. Challenge: Write a program that implements a dynamically growing array of strings.

Mini Project: Dynamic String Array

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

typedef struct {
    char **data;
    int count;
    int capacity;
} StringArray;

void init_array(StringArray *arr) {
    arr->data = NULL;
    arr->count = 0;
    arr->capacity = 0;
}

void add_string(StringArray *arr, const char *str) {
    if (arr->count >= arr->capacity) {
        arr->capacity = arr->capacity ? arr->capacity * 2 : 4;
        char **temp = realloc(arr->data, arr->capacity * sizeof(char*));
        if (!temp) return;
        arr->data = temp;
    }
    
    arr->data[arr->count] = malloc(strlen(str) + 1);
    if (arr->data[arr->count]) {
        strcpy(arr->data[arr->count], str);
        arr->count++;
    }
}

void free_array(StringArray *arr) {
    for (int i = 0; i < arr->count; i++) {
        free(arr->data[i]);
    }
    free(arr->data);
    init_array(arr);
}

int main() {
    StringArray arr;
    init_array(&arr);
    
    add_string(&arr, "Hello");
    add_string(&arr, "World");
    add_string(&arr, "Dynamic");
    add_string(&arr, "Array");
    
    for (int i = 0; i < arr.count; i++) {
        printf("arr[%d] = '%s'\n", i, arr.data[i]);
    }
    
    free_array(&arr);
    return 0;
}

FAQ

What is the heap?

The heap is the region of memory used for dynamic allocation. It is managed by malloc/free and is separate from the stack and static data segments.

Can I use memory after free?

No. That is a use-after-free bug and undefined behavior. The memory may be reused by other allocations.

What is a memory leak?

Memory that was allocated but never freed. The program loses track of it and cannot release it. Over time, the program consumes more and more memory.

How does realloc know how much memory was originally allocated?

The memory allocator stores metadata (size, bookkeeping info) in a header before the returned address. This is hidden from the programmer.

Is it safe to free(NULL)?

Yes. The C standard guarantees that free(NULL) does nothing. This is useful for cleanup code.

What is Next

Now that you understand dynamic memory, proceed to Memory Layout to learn about stack, heap, data segment, and text segment organization.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro

Home Browse C