C Dynamic Memory — Malloc, Calloc, Realloc, and Free Explained
In this tutorial, you will learn about C Dynamic Memory. We cover key concepts, practical examples, and best practices to help you master this topic.
C dynamic memory allocation uses malloc, calloc, realloc, and free to manage heap memory at runtime, enabling programs to handle data of unknown size and create data structures that persist beyond function scope.
Why It Matters
Static and stack allocation are not enough for real programs. You cannot know at compile time how many items a user will enter, how large a file will be, or how many connections a server must handle. Dynamic memory lets you allocate exactly what you need at runtime. Mismanaging dynamic memory leads to leaks, crashes, and security vulnerabilities.
Real-World Use
Every non-trivial C program uses dynamic memory. Web servers allocate connection structs dynamically. Databases allocate cache buffers. Image editors allocate pixel buffers sized by the image dimensions. Durga Antivirus Pro allocates signature databases dynamically as new threats are detected.
What You Will Learn
- Allocating memory with malloc
- Zero-initialized allocation with calloc
- Resizing allocations with realloc
- Deallocating memory with free
- Common memory management patterns
- Debugging memory errors
Learning Path
flowchart LR A[Functions & Pointers] --> B[Dynamic Memory
You are here] B --> C[Memory Layout] C --> D[Structs] D --> E[File I/O] style B fill:#f90,color:#fff
malloc -- Memory Allocation
malloc allocates a block of uninitialized memory and returns a void pointer to it:
#include <stdio.h>
#include <stdlib.h>
int main() {
int *arr;
int n = 5;
// Allocate memory for 5 integers
arr = (int*)malloc(n * sizeof(int));
// Always check if allocation succeeded
if (arr == NULL) {
printf("Memory allocation failed!\n");
return 1;
}
// Use the memory
for (int i = 0; i < n; i++) {
arr[i] = i * 10;
}
printf("Array elements: ");
for (int i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
printf("\n");
// Free the memory
free(arr);
return 0;
}
Expected output: Array elements: 0 10 20 30 40
Key Points About malloc
- Takes the number of bytes to allocate
- Returns
void*(implicitly convertible to any pointer type in C) - Returns NULL if allocation fails
- Memory content is uninitialized (contains garbage)
- Always use
sizeof(type)to calculate bytes - Casting the result is optional in C but recommended for clarity
calloc -- Clear Allocation
calloc allocates memory for an array of elements and zero-initializes all bytes:
#include <stdio.h>
#include <stdlib.h>
int main() {
int *arr;
int n = 5;
// Allocate and zero-initialize 5 integers
arr = (int*)calloc(n, sizeof(int));
if (arr == NULL) {
printf("Memory allocation failed!\n");
return 1;
}
// All elements are guaranteed to be 0
for (int i = 0; i < n; i++) {
printf("arr[%d] = %d\n", i, arr[i]);
}
free(arr);
return 0;
}
Expected output:
arr[0] = 0
arr[1] = 0
arr[2] = 0
arr[3] = 0
arr[4] = 0
malloc vs calloc
| Aspect | malloc | calloc |
|---|---|---|
| Arguments | Number of bytes | Count and element size |
| Initialization | Uninitialized (garbage) | Zero-initialized |
| Performance | Faster (no initialization) | Slower (writes zeros) |
| Use case | When you will initialize immediately | When you need zeroed memory |
realloc -- Reallocation
realloc resizes an existing allocation, preserving the old content:
#include <stdio.h>
#include <stdlib.h>
int main() {
int *arr;
int n = 3;
// Initial allocation
arr = (int*)malloc(n * sizeof(int));
if (arr == NULL) return 1;
arr[0] = 10;
arr[1] = 20;
arr[2] = 30;
// Expand to hold 6 elements
int new_n = 6;
int *temp = (int*)realloc(arr, new_n * sizeof(int));
if (temp == NULL) {
// realloc failed, original memory is still valid
printf("Reallocation failed!\n");
free(arr);
return 1;
}
arr = temp; // Update pointer
n = new_n;
// Assign new elements
arr[3] = 40;
arr[4] = 50;
arr[5] = 60;
printf("Expanded array: ");
for (int i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
printf("\n");
// Shrink
arr = realloc(arr, 4 * sizeof(int));
printf("Shrunk array: ");
for (int i = 0; i < 4; i++) {
printf("%d ", arr[i]);
}
printf("\n");
free(arr);
return 0;
}
Expected output:
Expanded array: 10 20 30 40 50 60
Shrunk array: 10 20 30 40
realloc Rules
- If the new size is larger, old content is preserved, new space is uninitialized
- If the new size is smaller, content is truncated
- If the pointer is NULL, realloc behaves like malloc
- If the size is 0, realloc behaves like free
- Returns NULL on failure, in which case the original pointer remains valid
- Always use a temporary pointer for realloc to avoid losing the original on failure
free -- Deallocation
free releases previously allocated memory back to the heap:
#include <stdio.h>
#include <stdlib.h>
int main() {
int *ptr = (int*)malloc(sizeof(int));
*ptr = 42;
printf("Value: %d\n", *ptr);
free(ptr); // Release memory
// ptr is now a dangling pointer!
// *ptr = 99; // Undefined behavior!
ptr = NULL; // Good practice: set to NULL after free
// free(NULL) is safe
free(NULL); // Does nothing
return 0;
}
Memory Management Rules
- Every
malloc/calloc/reallocmust have a matchingfree - Freeing the same memory twice is undefined behavior (double-free)
- Accessing freed memory is undefined behavior (use-after-free)
- Setting pointer to NULL after free prevents accidental reuse
- Forgetting to free causes memory leaks
Common Allocation Patterns
Pattern 1: Dynamic Array
#include <stdio.h>
#include <stdlib.h>
int main() {
int *arr = NULL;
int count = 0;
int capacity = 0;
int input;
printf("Enter numbers (negative to stop):\n");
while (1) {
scanf("%d", &input);
if (input < 0) break;
if (count >= capacity) {
capacity = capacity ? capacity * 2 : 4;
int *temp = realloc(arr, capacity * sizeof(int));
if (!temp) {
printf("Out of memory!\n");
free(arr);
return 1;
}
arr = temp;
}
arr[count++] = input;
}
printf("You entered: ");
for (int i = 0; i < count; i++) {
printf("%d ", arr[i]);
}
printf("\n");
free(arr);
return 0;
}
Pattern 2: Allocating for a Function
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
char *name;
int age;
} Person;
Person *create_person(const char *name, int age) {
Person *p = malloc(sizeof(Person));
if (!p) return NULL;
p->name = malloc(strlen(name) + 1);
if (!p->name) {
free(p);
return NULL;
}
strcpy(p->name, name);
p->age = age;
return p;
}
void destroy_person(Person *p) {
if (p) {
free(p->name);
free(p);
}
}
int main() {
Person *alice = create_person("Alice", 30);
if (alice) {
printf("%s is %d years old\n", alice->name, alice->age);
destroy_person(alice);
}
return 0;
}
Memory Leak Detection
Valgrind is the standard tool for detecting memory errors:
gcc -g -o program program.c
valgrind --leak-check=full ./program
Valgrind reports:
- Memory leaks (allocated but never freed)
- Invalid reads/writes
- Use-after-free errors
- Double frees
Common Mistakes
1. Forgetting to Free Memory
void leak() {
int *p = malloc(1000);
// p is lost when function returns -- memory leak!
}
Every allocation must have a corresponding free.
2. Using Freed Memory
int *p = malloc(sizeof(int));
free(p);
*p = 42; // Use-after-free!
Set p to NULL after freeing.
3. Double Free
free(p);
free(p); // Undefined behavior!
Free each pointer exactly once.
4. Not Checking malloc Return
int *p = malloc(1000000000000); // May fail!
p[0] = 42; // Crash if NULL
Always check for NULL after allocation.
5. Buffer Overflow on Heap
int *p = malloc(5 * sizeof(int));
p[5] = 42; // Writing past allocated region
Heap corruption may crash later, making debugging difficult.
6. Calling realloc Without Temporary Variable
ptr = realloc(ptr, new_size); // If realloc fails, ptr is lost!
Use a temporary pointer to avoid losing the original allocation.
Practice Questions
What does malloc return if memory allocation fails? NULL. Always check the return value before using the allocated memory.
What is the difference between malloc and calloc? malloc returns uninitialized memory. calloc zero-initializes the memory and takes two arguments (count and size).
What should you do after calling free on a pointer? Set the pointer to NULL to prevent accidental use-after-free.
What happens if you forget to free memory? The memory remains allocated until the program exits. Long-running programs will eventually exhaust available memory.
Challenge: Write a program that implements a dynamically growing array of strings.
Mini Project: Dynamic String Array
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
char **data;
int count;
int capacity;
} StringArray;
void init_array(StringArray *arr) {
arr->data = NULL;
arr->count = 0;
arr->capacity = 0;
}
void add_string(StringArray *arr, const char *str) {
if (arr->count >= arr->capacity) {
arr->capacity = arr->capacity ? arr->capacity * 2 : 4;
char **temp = realloc(arr->data, arr->capacity * sizeof(char*));
if (!temp) return;
arr->data = temp;
}
arr->data[arr->count] = malloc(strlen(str) + 1);
if (arr->data[arr->count]) {
strcpy(arr->data[arr->count], str);
arr->count++;
}
}
void free_array(StringArray *arr) {
for (int i = 0; i < arr->count; i++) {
free(arr->data[i]);
}
free(arr->data);
init_array(arr);
}
int main() {
StringArray arr;
init_array(&arr);
add_string(&arr, "Hello");
add_string(&arr, "World");
add_string(&arr, "Dynamic");
add_string(&arr, "Array");
for (int i = 0; i < arr.count; i++) {
printf("arr[%d] = '%s'\n", i, arr.data[i]);
}
free_array(&arr);
return 0;
}
FAQ
What is Next
Now that you understand dynamic memory, proceed to Memory Layout to learn about stack, heap, data segment, and text segment organization.