Lesson 117 min read

Dynamic Memory

The stack is your desk — small and automatic. The heap is a warehouse — huge, but you manage it yourself.

Your Desk vs. The Warehouse

When you declare a variable like int x = 5;, it lives on the stack — think of it as your desk. It's fast, tidy, and automatically cleaned up when you leave the room (when the function returns). But your desk is small. You can't fit a million sticky notes on it.

The heap is like a giant warehouse. It has tons of space, but there's a catch: you have to reserve shelves, you have to label them, and you have to put things back when you're done. Forget to clean up? Congratulations, you've got a memory leak.

The Four Functions

C gives you four tools to manage the warehouse:

  • malloc — reserve a chunk of memory (uninitialized — could contain garbage)
  • calloc — reserve AND zero-initialize memory
  • realloc — resize a previously allocated chunk
  • free — return memory back to the system

All four live in <stdlib.h>.

Allocating a Single Integer

#include <stdio.h>
#include <stdlib.h>
int main() {
// Allocate space for one int on the heap
int *p = (int *)malloc(sizeof(int));
if (p == NULL) {
printf("Memory allocation failed!\n");
return 1;
}
*p = 42;
printf("Value: %d\n", *p);
printf("Address: %p\n", (void *)p);
free(p); // Always free what you malloc!
return 0;
}
Output
Value: 42
Address: 0x55a3b2c04260

malloc — Raw Memory

malloc(size) allocates size bytes and returns a pointer to the first byte. The memory is not initialized — it contains whatever garbage was there before. Think of it as renting a storage locker that the previous tenant never cleaned out.

calloc — Clean Memory

calloc(count, size) allocates space for count items of size bytes each, and sets every byte to zero. It's like renting a locker that's been freshly swept. Use this when you want an array initialized to zero.

Dynamic Array with malloc vs calloc

#include <stdio.h>
#include <stdlib.h>
int main() {
int n = 5;
// malloc: uninitialized (garbage values!)
int *arr1 = (int *)malloc(n * sizeof(int));
// calloc: zero-initialized
int *arr2 = (int *)calloc(n, sizeof(int));
if (!arr1 || !arr2) {
printf("Allocation failed!\n");
return 1;
}
// Fill the malloc array
for (int i = 0; i < n; i++) {
arr1[i] = (i + 1) * 10;
}
printf("malloc array: ");
for (int i = 0; i < n; i++) printf("%d ", arr1[i]);
printf("\ncalloc array: ");
for (int i = 0; i < n; i++) printf("%d ", arr2[i]);
printf("\n");
free(arr1);
free(arr2);
return 0;
}
Output
malloc array: 10 20 30 40 50
calloc array: 0 0 0 0 0
Note: Every malloc needs a free! Memory leaks are the #1 bug in C programs. If you allocate memory and lose the pointer to it, that memory is gone until the program exits. Always check if malloc returns NULL — it means the system is out of memory.

realloc — Growing and Shrinking

What if you allocated space for 5 items but now need 10? That's where realloc comes in. It takes an existing allocation and resizes it — possibly moving the data to a new location if the current block can't grow in-place.

Think of it like asking the warehouse manager: "I need a bigger shelf." They might extend your current shelf, or they might move everything to a bigger one across the room and hand you the new address.

Growing an Array with realloc

#include <stdio.h>
#include <stdlib.h>
int main() {
int capacity = 3;
int *arr = (int *)malloc(capacity * sizeof(int));
if (!arr) return 1;
// Fill initial array
arr[0] = 10; arr[1] = 20; arr[2] = 30;
printf("Before realloc: ");
for (int i = 0; i < capacity; i++) printf("%d ", arr[i]);
printf("(capacity: %d)\n", capacity);
// Double the capacity
capacity *= 2;
int *temp = (int *)realloc(arr, capacity * sizeof(int));
if (!temp) {
free(arr); // realloc failed; original still valid
return 1;
}
arr = temp;
// Fill the new slots
arr[3] = 40; arr[4] = 50; arr[5] = 60;
printf("After realloc: ");
for (int i = 0; i < capacity; i++) printf("%d ", arr[i]);
printf("(capacity: %d)\n", capacity);
free(arr);
return 0;
}
Output
Before realloc: 10 20 30 (capacity: 3)
After realloc:  10 20 30 40 50 60 (capacity: 6)

Dangling Pointers

After you free(p), the memory is returned to the system — but p still holds the old address. Using p after freeing it is like going back to your old apartment after moving out and trying to sit on the couch. The couch might still be there... or someone else might be living there now. This is a dangling pointer — it points to memory that's no longer yours.

Best practice: set the pointer to NULL after freeing.

Proper Cleanup Pattern

#include <stdio.h>
#include <stdlib.h>
int main() {
int *data = (int *)malloc(100 * sizeof(int));
if (!data) {
printf("Allocation failed\n");
return 1;
}
// ... use data ...
data[0] = 999;
printf("data[0] = %d\n", data[0]);
// Proper cleanup
free(data);
data = NULL; // Prevent dangling pointer
// Safe: checking before use
if (data != NULL) {
printf("data[0] = %d\n", data[0]);
} else {
printf("data has been freed\n");
}
return 0;
}
Output
data[0] = 999
data has been freed

Stack vs Heap — When to Use Which

Use the stack (normal variables) when:

  • You know the size at compile time
  • The data is small (a few ints, a small array)
  • The data only needs to live inside one function

Use the heap (malloc/calloc) when:

  • You don't know the size until runtime (user input decides)
  • The data is large (thousands of elements)
  • The data needs to outlive the function that created it
Note: Never realloc directly into the same pointer: arr = realloc(arr, newSize). If realloc fails, it returns NULL and you lose your only reference to the original memory — instant memory leak! Always use a temp pointer.
Challenge

Quick check

What is the key difference between malloc and calloc?
Pointer Arithmetic & ArraysStructs & Typedef