Code of the Day
IntermediateMemory management

malloc, calloc, realloc, and free

Allocate and manage heap memory in C using malloc, calloc, realloc, and free — including correct usage patterns and common mistakes.

CIntermediate12 min read
Recommended first
By the end of this lesson you will be able to:
  • Use malloc to allocate a block of uninitialized memory
  • Use calloc to allocate zero-initialized memory
  • Use realloc to resize an existing allocation
  • Use free to return memory to the allocator
  • Explain what happens when allocation fails and check for it

The four memory management functions in <stdlib.h> are the entire API for heap memory in C. There is no garbage collector — you call malloc to get memory and free to return it. Nothing happens automatically. This section covers exactly what each function does and how to use it without causing bugs.

malloc — allocate memory

void *malloc(size_t size);

malloc allocates size bytes of uninitialized heap memory and returns a pointer to it. Returns NULL if allocation fails.

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

int main(void) {
    int n = 10;

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

    /* Initialise manually -- malloc does not zero the memory */
    for (int i = 0; i < n; i++) {
        arr[i] = i * i;
    }

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

    free(arr);
    return 0;
}

Key points:

  • malloc returns void *, which is implicitly converted to any pointer type.
  • The memory is not zeroed — it contains whatever bytes were there before.
  • Always check for NULL.
  • Always free what you malloc.

calloc — allocate and zero-initialise

void *calloc(size_t count, size_t size);

calloc allocates space for count elements of size bytes each, zeroing all bytes:

int *arr = calloc(10, sizeof(int));
if (arr == NULL) { /* handle error */ }
/* arr[0] through arr[9] are all 0 */
free(arr);

Use calloc when you need zeroed memory (e.g., for a boolean flags array or when zero is a meaningful sentinel). It is slightly slower than malloc because it writes zeros.

malloc(n * sizeof(int)) and calloc(n, sizeof(int)) allocate the same amount of memory; calloc just guarantees zeros. calloc is also slightly safer: n * sizeof(int) can overflow for large n, whereas calloc(n, sizeof(int)) is required to detect the overflow (and return NULL).

realloc — resize an allocation

void *realloc(void *ptr, size_t new_size);

realloc resizes the block pointed to by ptr to new_size bytes. It may return the same pointer (if the block can be expanded in place) or a new pointer (if it had to move the block):

int *arr = malloc(5 * sizeof(int));
if (!arr) { /* handle error */ }

/* Later, we need more space */
int *tmp = realloc(arr, 10 * sizeof(int));
if (tmp == NULL) {
    free(arr); /* original block is unchanged on failure */
    fprintf(stderr, "realloc failed\n");
    return 1;
}
arr = tmp; /* safe to reassign only after checking NULL */

Never do arr = realloc(arr, ...). If realloc returns NULL (failure), you have overwritten arr with NULL and lost your only pointer to the original block — a memory leak. Always use a temporary variable to hold the return value, check for NULL, then reassign.

When realloc moves a block, the old pointer is invalidated — any other pointers to that memory become dangling. Keep only one pointer to each allocation.

free — return memory

void free(void *ptr);

free returns the memory block to the heap allocator:

free(arr);
arr = NULL; /* defensive: prevents accidental use-after-free */

Rules:

  • Only free pointers returned by malloc, calloc, or realloc.
  • Never free a stack variable's address.
  • Never free the same pointer twice (double free = undefined behaviour).
  • free(NULL) is always safe and does nothing.

Allocating a string on the heap

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

char *duplicate_string(const char *src) {
    size_t len = strlen(src);
    char *copy = malloc(len + 1); /* +1 for null terminator */
    if (copy == NULL) { return NULL; }
    strcpy(copy, src);
    return copy;
}

int main(void) {
    char *s = duplicate_string("Hello, World!");
    if (s) {
        printf("%s\n", s);
        free(s);
    }
    return 0;
}

This is the pattern for strdup (which is available as a POSIX extension but not in standard C89/C90).

Allocating a 2D array

A dynamically-allocated 2D array requires a pointer to pointers:

int rows = 3, cols = 4;

/* Allocate array of row pointers */
int **matrix = malloc(rows * sizeof(int *));
if (!matrix) { /* handle error */ }

/* Allocate each row */
for (int r = 0; r < rows; r++) {
    matrix[r] = malloc(cols * sizeof(int));
    if (!matrix[r]) { /* handle error + free previous rows */ }
}

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

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

Alternatively, allocate a single contiguous block and compute indices manually — this is faster due to fewer allocations and better cache locality.

Where to go next

Next: memory leaks and how to find them — what happens when you forget to free memory, and how tools like valgrind detect leaks.

Finished reading? Mark it complete to track your progress.

On this page