Three functions. One job: allocate heap memory. If you are unsure whether you need heap memory at all, start with our guide on stack vs heap in C. They are not interchangeable. Using the wrong one leads to uninitialized reads, wasted zeroing operations, or memory leaks. Here is how each one actually works.

malloc — Raw Allocation

void *malloc(size_t size);

malloc allocates size bytes of memory on the heap and returns a pointer to it. The memory is not initialized — it contains whatever bytes were left there by previous allocations or deallocations.

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

int main() {
    int *arr = malloc(5 * sizeof(int));
    if (arr == NULL) {
        fprintf(stderr, "malloc failedn");
        return 1;
    }

    /* contents are undefined — do not read before writing */
    for (int i = 0; i < 5; i++) {
        arr[i] = i * 10;
    }

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

    free(arr);
    return 0;
}

Always check the return value. malloc returns NULL if it cannot allocate the requested memory. Reading from or writing to a NULL pointer is undefined behaviour.

calloc — Zero-Initialized Allocation

void *calloc(size_t nmemb, size_t size);

calloc allocates memory for nmemb elements of size bytes each and sets every byte to zero. The total allocation is nmemb * size bytes.

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

int main() {
    int *arr = calloc(5, sizeof(int));
    if (arr == NULL) {
        fprintf(stderr, "calloc failedn");
        return 1;
    }

    /* all elements are guaranteed to be 0 */
    for (int i = 0; i < 5; i++) {
        printf("%d ", arr[i]);   /* prints: 0 0 0 0 0 */
    }
    printf("n");

    free(arr);
    return 0;
}

calloc also handles the multiplication for you. This matters: malloc(n * size) can overflow if n * size exceeds SIZE_MAX. calloc(n, size) detects this overflow and returns NULL instead of allocating the wrong amount.

When to use calloc over malloc

  • When you need a zero-initialized array and do not want to call memset separately
  • When you are allocating a struct and want all fields to start at zero/NULL
  • When you are allocating large arrays and want protection against integer overflow in the size calculation

calloc is not always slower

A common assumption: calloc is slower because it zeroes memory. In practice, the OS already provides zeroed pages to new processes for security reasons. calloc implementations on Linux and macOS can request memory from the OS that is already zeroed, making calloc as fast as malloc in many cases.

realloc — Resize an Existing Allocation

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

realloc resizes a previously allocated block. It may move the block to a new location if there is not enough space to expand in place. The returned pointer may differ from the input pointer.

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

int main() {
    int *arr = malloc(3 * sizeof(int));
    if (arr == NULL) return 1;

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

    /* grow the array to hold 6 elements */
    int *tmp = realloc(arr, 6 * sizeof(int));
    if (tmp == NULL) {
        /* realloc failed — original arr is still valid */
        free(arr);
        return 1;
    }
    arr = tmp;   /* safe to update now */

    arr[3] = 40;
    arr[4] = 50;
    arr[5] = 60;

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

    free(arr);
    return 0;
}

The realloc mistake everyone makes

/* WRONG — do not do this */
arr = realloc(arr, new_size);
/* If realloc returns NULL, arr is now NULL.
   The original memory is leaked. */

Always assign to a temporary pointer first. If realloc fails, the original block is still valid and must be freed. Assigning directly to arr loses the original pointer.

Special cases for realloc

/* realloc with NULL is identical to malloc */
int *p = realloc(NULL, 100);   /* same as malloc(100) */

/* realloc with size 0 frees the block (implementation-defined) */
realloc(p, 0);   /* avoid this — use free(p) instead, it is clearer */

Quick Comparison

Function Initializes memory Can resize Overflow-safe size calc
malloc(size) No No No
calloc(n, size) Yes (zero) No Yes
realloc(ptr, size) No (new bytes undefined) Yes No

Always Free What You Allocate

#include <stdlib.h>

int main() {
    char *buf = malloc(256);
    if (buf == NULL) return 1;

    /* ... use buf ... */

    free(buf);
    buf = NULL;   /* good habit — prevents use-after-free */
    return 0;
}

Set the pointer to NULL after freeing. It does not free the memory again, but it ensures that a second accidental free(buf) call is a no-op (freeing NULL is defined to do nothing) rather than undefined behaviour.

TL;DR

  • Use malloc when you will write to every byte before reading — no zeroing needed
  • Use calloc when you need zero-initialized memory or want overflow-safe size arithmetic
  • Use realloc to grow or shrink an existing allocation — always through a temporary pointer
  • Check every return value for NULL
  • Free every allocation exactly once