How Do I Allocate and Free Memory Dynamically?
calloc, malloc, free — manual memory management where Java used garbage collection
After this lesson, you will be able to:
- Allocate heap memory using
callocand explain why it is preferred overmalloc - Write a
NULLcheck after every allocation and handle failure - Identify memory leaks, use-after-free, and double free errors
- Implement the allocate-use-free pattern with
NULLafter free
What If You Don’t Know the Size at Compile Time?
Until now, every array you’ve declared had a fixed size:
int grades[100]; // Hope 100 is enough!
But what if the user enters 200 grades? Or 5? A fixed-size array either wastes memory or overflows. You need arrays whose size is determined at runtime.
That’s what dynamic memory allocation gives you — the ability to request exactly the amount of memory you need, when you need it.
calloc, malloc, and free
calloc: Allocate and Zero-Initialize
#include <stdlib.h>
int *grades = calloc(num_students, sizeof(int));
calloc(count, size) allocates count * size bytes on the heap and initializes them all to zero. It returns a pointer to the allocated block, or NULL if allocation fails.
int num = 5;
int *arr = calloc(num, sizeof(int));
if (arr == NULL)
{
printf("Memory allocation failed!\n");
return 1;
}
// Use arr like a normal array
arr[0] = 90;
arr[1] = 85;
arr[2] = 77;
// When done:
free(arr);
Key Insight:
callocis preferred in this course because it zero-initializes all memory.mallocleaves memory uninitialized (garbage values). Zero-initialization prevents entire classes of bugs where you accidentally read uninitialized data.
malloc: Allocate Without Initializing
int *arr = malloc(num * sizeof(int)); // NOT zero-initialized!
malloc(bytes) allocates bytes of memory but doesn’t initialize it. You must set every element before reading it.
Always Check for NULL
Allocation can fail (out of memory). Always check:
int *data = calloc(n, sizeof(int));
if (data == NULL)
{
fprintf(stderr, "Error: out of memory\n");
exit(1);
}
Common Pitfall: Ignoring the NULL check feels safe on your personal machine with gigabytes of RAM. But on embedded systems, servers under load, or when allocating large blocks,
calloccan return NULL. Professional C code always checks.
int *arr = calloc(5, sizeof(int));printf("%d\n", arr[3]);calloc zero-initializes all allocated bytes, so arr[3] is 0. If this were malloc, option B would be correct (garbage values). Options C and D are wrong because arr[3] is valid syntax — arr[3] is *(arr + 3), and pointer indexing works the same as array indexing.
free: Release Memory
When you’re done with allocated memory, release it:
free(arr);
arr = NULL; // Good practice: prevent use-after-free
Key Insight: Every
callocmust have a matchingfree. Memory that’s allocated but never freed is a memory leak — the program gradually consumes more and more memory until it slows down or crashes. Java’s garbage collector prevents this automatically; in C, it’s your responsibility.
The Complete Pattern
// 1. Allocate
int *scores = calloc(num_students, sizeof(int));
if (scores == NULL) { /* handle error */ }
// 2. Use
for (int i = 0; i < num_students; i++)
{
scores[i] = /* some value */;
}
// 3. Free
free(scores);
scores = NULL;
Why calloc Over malloc?
| Feature | calloc |
malloc |
|---|---|---|
| Syntax | calloc(count, size) |
malloc(count * size) |
| Initialization | Zeros all bytes | No initialization |
| Overflow check | Checks count * size overflow |
No overflow check |
From Java:
new int[100]in Java allocates 100 ints on the heap and zero-initializes them.calloc(100, sizeof(int))does exactly the same thing — except you must callfreelater because there’s no garbage collector. Think ofcallocas Java’snewwithout the safety net.
Returning Heap Memory from Functions
Unlike local variables, heap memory persists after the function returns:
int *create_array(int size)
{
int *arr = calloc(size, sizeof(int));
// arr lives on the heap — it survives after this function returns
return arr;
}
int main(void)
{
int *data = create_array(50);
data[0] = 42; // Safe! Memory is still valid
free(data); // Caller is responsible for freeing
return 0;
}
The Trick: When a function allocates memory and returns a pointer, the caller becomes responsible for freeing it. Document this in comments or function names (e.g.,
create_arrayimplies the caller mustfreethe result).
int *data = calloc(10, sizeof(int));/* ... use data ... */free(data);data[0] = 99;free(data), the memory is released back to the system. data still holds the old address (a dangling pointer), but writing to it is undefined behavior — it might corrupt other data, crash, or silently "work." The fix: set data = NULL after freeing so any accidental access is a clean segfault. Option D is dangerously wrong — freed memory can be reallocated to something else at any time.
Why does this matter?
Use-after-free bugs are a top source of security vulnerabilities. Attackers can exploit them to execute arbitrary code. Setting freed pointers to NULL is a simple habit that turns invisible corruption into a loud, debuggable crash.
Common Memory Errors
Memory leak — allocate but never free:
int *p = calloc(100, sizeof(int));
// ... forget to call free(p)
// Memory leaked until program exits
Use after free — access freed memory:
free(p);
p[0] = 42; // UNDEFINED BEHAVIOR: p points to freed memory
Double free — free the same block twice:
free(p);
free(p); // UNDEFINED BEHAVIOR: double free
Setting pointer to NULL after free prevents both use-after-free and double-free — free(NULL) is safe (does nothing).
Quick Check: What's the difference between calloc(10, sizeof(int)) and malloc(10 * sizeof(int))?
Both allocate 40 bytes. calloc zeros all bytes; malloc leaves them uninitialized (garbage). calloc also internally checks for integer overflow in 10 * sizeof(int); malloc does not.
Quick Check: What happens if you forget to call free?
The memory is leaked — it stays allocated but is no longer accessible (if you’ve lost the pointer). For short-lived programs, the OS reclaims everything at exit. For long-running programs (servers, daemons), leaks accumulate and eventually consume all available memory.
Quick Check: Why set a pointer to NULL after freeing?
It prevents two bugs: (1) use-after-free — dereferencing NULL crashes immediately and predictably, instead of silently corrupting memory. (2) double-free — free(NULL) is safe (defined as a no-op), so accidentally freeing twice doesn’t cause undefined behavior.
With Great Power Comes Great Responsibility
Dynamic memory allocation gives you flexibility that Java hides behind new and the garbage collector. But every calloc needs a free, every pointer needs a NULL check, and every freed pointer should be set to NULL.
Next: realloc and memcpy — growing arrays at runtime. This is what Java’s ArrayList does behind the scenes when it runs out of room.
Big Picture: Dynamic memory is where C stops being “a nicer assembly language” and becomes a tool for building real software. Every data structure you’ll build — dynamic arrays, linked lists, hash tables, trees — lives on the heap. The allocate-use-free pattern from this lesson is the backbone of every C program that handles variable-sized data.