Part of C in 100s

C in 100 Seconds: calloc and realloc | Episode 21

Celest KimCelest Kim

Video: C in 100 Seconds: calloc realloc — Zero Init and Resize | Episode 21 by Taught by Celeste AI - AI Coding Coach

Take the quiz on the full lesson page
Test what you've read · interactive walkthrough

C calloc and realloc: Zero Init and Resize

calloc(n, size) allocates and zeros n * size bytes. realloc(ptr, size) resizes an existing allocation. The two cousins of malloc for cleaner allocation patterns.

malloc is the workhorse, but calloc and realloc cover two specific patterns: "I want zeros" and "I need to grow."

The basic shape

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

int main() {
  int *nums = calloc(5, sizeof(int));

  printf("calloc zeros everything:\n");
  for (int i = 0; i < 5; i++) {
    printf("  nums[%d] = %d\n", i, nums[i]);   // all 0
  }

  nums = realloc(nums, 8 * sizeof(int));
  nums[5] = 50;
  nums[6] = 60;
  nums[7] = 70;

  printf("\nAfter realloc to 8:\n");
  for (int i = 0; i < 8; i++) {
    printf("  nums[%d] = %d\n", i, nums[i]);
  }

  free(nums);
  return 0;
}

calloc

void *calloc(size_t count, size_t size);

Allocates count * size bytes, zeroed. Returns NULL on failure.

int *nums = calloc(5, sizeof(int));
// nums is a 5-element zeroed int array

Pros over malloc:

  • Zero-initialized. No garbage; safer for code that might read before writing.
  • Overflow-checked multiplication. calloc(huge, huge) returns NULL instead of allocating a wrap-around tiny block.

Cons:

  • Slightly slower (zeroing takes time).
  • For some allocators, the OS may already give you zeroed pages (so the "slower" is illusory).

For "I'm about to write to every byte anyway," malloc is fine. For "I want a clean slate," calloc.

malloc + memset = ~calloc

int *nums = malloc(5 * sizeof(int));
memset(nums, 0, 5 * sizeof(int));

Equivalent to calloc(5, sizeof(int)). The memset zeros the bytes. calloc is shorter and may be faster on systems that get zeroed pages from the OS.

realloc

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

Resizes the allocation ptr points to. Three behaviors:

  1. Grow in place — if there's room, the same address is returned with extended size.
  2. Move to a bigger block — old contents copied; old block freed; new pointer returned.
  3. Shrink — same address, smaller size.
int *nums = malloc(5 * sizeof(int));
// ... fill nums[0..4] ...

nums = realloc(nums, 10 * sizeof(int));
// nums now points to (possibly new) 10-element block; first 5 still valid

If ptr is NULL, realloc behaves like malloc(new_size).

If new_size is 0, behavior depends on the C standard version — usually frees ptr and returns NULL. Don't rely on this.

The realloc trap

nums = realloc(nums, new_size);   // BAD if realloc fails!

If realloc fails, it returns NULL — but the original nums is still valid (allocation untouched). By overwriting nums with NULL, you've leaked the original allocation.

The safe pattern:

int *tmp = realloc(nums, new_size);
if (tmp == NULL) {
  // realloc failed; nums is still valid
  fprintf(stderr, "realloc failed\n");
  free(nums);   // release the original
  return 1;
}
nums = tmp;

Two-step assignment avoids leaks on failure.

Growing dynamic arrays

The classic dynamic array:

typedef struct {
  int *data;
  size_t size;
  size_t capacity;
} Vec;

void vec_push(Vec *v, int value) {
  if (v->size == v->capacity) {
    size_t new_cap = v->capacity == 0 ? 4 : v->capacity * 2;
    int *tmp = realloc(v->data, new_cap * sizeof(int));
    if (tmp == NULL) abort();
    v->data = tmp;
    v->capacity = new_cap;
  }
  v->data[v->size++] = value;
}

When full, double the capacity. Amortized O(1) per push (most pushes are fast; occasional reallocations are O(n) but rare). Episode 56 explores this in depth.

Shrinking with realloc

nums = realloc(nums, 3 * sizeof(int));

realloc(ptr, smaller_size) shrinks the allocation. The first smaller_size / sizeof(int) elements are preserved.

Most allocators don't actually return memory to the OS on shrink — they keep it in their free list. For most code, this is fine. For very long-running programs, occasional shrinks help fragmentation.

When ptr is NULL

int *p = NULL;
p = realloc(p, 10 * sizeof(int));
// equivalent to: p = malloc(10 * sizeof(int))

realloc(NULL, size) behaves like malloc(size). Useful in init code that might be called multiple times — you don't need to handle "first time" specially.

Common mistakes

Forgetting to track the new size. After realloc, the array is bigger, but if you forget to update your size variable, you'll read stale data or write past it.

Realloc trap (assigning back without temp). If realloc fails, NULL overwrites the still-valid original pointer. Use a temporary variable.

Realloc and pointer-into-the-array invalidates. int *p = &nums[2]; nums = realloc(...); *p = ...; — if realloc moved the buffer, p now points into freed memory. Recompute pointers after realloc.

Calloc with overflow. calloc(SIZE_MAX, 2) returns NULL because SIZE_MAX * 2 would overflow. Check the return.

Mismatched calloc/malloc with free. All three (malloc, calloc, realloc) produce memory that free releases. No special "calloc-free" — just free.

Treating shrunken realloc as guaranteed return. Not all realloc shrink calls actually shrink the OS allocation. The pointer is still valid; the rest of the question (whether the OS got memory back) is implementation-defined.

What about aligned allocation?

For SIMD or specific hardware, you may need aligned memory:

#include <stdlib.h>
void *p = aligned_alloc(64, 1024);   // 1024 bytes, 64-byte aligned (C11)

Or posix_memalign on POSIX systems, _aligned_malloc on Windows. Free with free (or _aligned_free on Windows).

What's next

Episode 22: memory leaks. What happens when you forget free. How to detect them.

Recap

calloc(n, size) for zeroed allocation; checks for overflow. realloc(ptr, new_size) resizes — may move; if NULL ptr, acts like malloc. Always use a temporary variable for realloc to avoid leaking on failure. After realloc, any pointers into the old buffer are invalid. Free with free for all three (malloc, calloc, realloc). Pre-double the capacity for amortized O(1) growth.

Next episode: memory leaks.

Ready? Take the quiz on the full lesson page →
Test what you've learned. Watch the lesson and try the interactive quiz on the same page.