Dynamic Memory Allocation
Allocate memory at runtime! Use malloc, calloc, realloc, and free. Create arrays whose size you don't know until the program runs.
What You Will Learn
- ✓Allocate memory with malloc and calloc
- ✓Resize memory with realloc
- ✓Free memory to prevent leaks
- ✓Understand Stack vs Heap allocation
01Static vs Dynamic Memory
📚 C Has Two Types of Memory
The process of reserving memory is called allocation. C has two types: Static memory (compile time) and Dynamic memory (runtime).
📦 Static Memory (Compile Time)
Static memory is reserved before the program runs. C automatically allocates memory for every variable when compiled.
1#include <stdio.h>23int main() {4 // Static allocation: 20 students = 80 bytes5 int students[20];6 printf("Size: %zu bytes\n", sizeof(students));7 8 // Problem: Only 12 students enrolled!9 // 8 slots wasted (32 bytes)10 return 0;11}Size: 80 bytes
⚠️ Problem with Static Memory
You reserved space for 20 students but only 12 enrolled. 8 slots wasted! And you can't change the array size later.
🔄 Dynamic Memory (Runtime)
Dynamic memory is allocated after the program starts running. You have full control over how much memory is used at any time.
1#include <stdio.h>2#include <stdlib.h>34int main() {5 int numStudents = 12; // Actual count6 7 // Allocate exactly what we need!8 int *students = calloc(numStudents, sizeof(int));9 10 printf("Size: %zu bytes\n", numStudents * sizeof(int));11 12 free(students);13 return 0;14}Size: 48 bytes
| Feature | Static Memory | Dynamic Memory |
|---|---|---|
| When allocated | Compile time | Runtime |
| Size | Fixed | Can change (realloc) |
| Memory location | Stack | Heap |
| Access | Variable name | Pointers only |
| Deallocation | Automatic | Manual (free) |
| Example | int arr[20]; | malloc(20*sizeof(int)) |
✅ Key Advantages of Dynamic Memory
- 1.Efficient: Allocate exactly what you need, no waste
- 2.Resizable: Grow or shrink arrays with realloc()
- 3.Persistent: Memory survives after function returns
- 4.Large: Heap is much bigger than stack
| Function | Purpose | Header |
|---|---|---|
| malloc() | Allocate memory (uninitialized) | <stdlib.h> |
| calloc() | Allocate + initialize to zero | <stdlib.h> |
| realloc() | Resize existing memory | <stdlib.h> |
| free() | Release memory back to system | <stdlib.h> |
02Stack vs Heap Memory
🧠 Understanding Memory Regions
Your program has two main memory areas: Stack (automatic, fast, limited) and Heap (manual, slower, large).
Memory Layout of a C Program:
Local variables, function calls
Dynamic memory (malloc, calloc)
| Feature | Stack | Heap |
|---|---|---|
| Management | Automatic | Manual (you manage) |
| Speed | Very Fast | Slower |
| Size | Limited (~1-8 MB) | Large (GB+) |
| Lifetime | Until function ends | Until you free() |
| Use for | Local variables | Large/dynamic data |
1#include <stdio.h>2#include <stdlib.h>34int main() {5 // STACK: automatic, fixed size6 int a = 10; // On stack7 int arr[5] = {1,2,3,4,5}; // On stack8 9 // HEAP: manual, dynamic size10 int *p = malloc(sizeof(int)); // On heap11 *p = 20;12 13 printf("Stack: a = %d\n", a);14 printf("Heap: *p = %d\n", *p);15 16 free(p); // Must free heap memory!17 return 0;18}Stack: a = 10
Heap: *p = 20
03malloc() - Memory Allocation
malloc() stands for "memory allocation". It allocates a single block of contiguous memory on the heap. The memory is uninitialized (contains garbage values).
📝 Syntax
void* malloc(size_t size);
- • size: Number of bytes to allocate
- • Returns:
void*pointer (needs type casting), orNULLif failed - • Warning: Memory is NOT initialized (contains garbage)
Step 1: Basic malloc (Not Recommended)
To store 5 integers, we need 5 × 4 = 20 bytes. This works but is not portable:
1#include <stdio.h>2#include <stdlib.h>34int main() {5 // Hardcoded 20 bytes (5 integers × 4 bytes)6 // Problem: int size varies by system!7 int *ptr = (int *)malloc(20);8 9 for (int i = 0; i < 5; i++)10 ptr[i] = i + 1;11 12 for (int i = 0; i < 5; i++)13 printf("%d ", ptr[i]);14 15 free(ptr);16 return 0;17}1 2 3 4 5
Step 2: Using sizeof() (Better)
The size of int depends on the architecture. Use sizeof() for portable code:
1#include <stdio.h>2#include <stdlib.h>34int main() {5 // sizeof(int) = 4 bytes on most systems6 int *ptr = (int *)malloc(sizeof(int) * 5);7 8 for (int i = 0; i < 5; i++)9 ptr[i] = i + 1;10 11 for (int i = 0; i < 5; i++)12 printf("%d ", ptr[i]);13 14 free(ptr);15 return 0;16}Step 3: Check for NULL (Recommended)
If there's no memory available, malloc returns NULL. Always check!
1#include <stdio.h>2#include <stdlib.h>34int main() {5 int *ptr = (int *)malloc(sizeof(int) * 5);6 7 // Always check for failure!8 if (ptr == NULL) {9 printf("Allocation Failed!\n");10 return 1;11 }12 13 for (int i = 0; i < 5; i++)14 ptr[i] = i + 1;15 16 for (int i = 0; i < 5; i++)17 printf("%d ", ptr[i]);18 19 free(ptr);20 return 0;21}💡 Type Casting
malloc() returns void*. In C, you can assign it directly to any pointer type. The cast (int *)is optional in C but required in C++.
How malloc() works:
int *ptr = (int *)malloc(sizeof(int) * 5);
ptr points to:
Garbage values! Not initialized
04calloc() - Contiguous Allocation
📝 Syntax
void* calloc(size_t count, size_t size);
- • count: Number of elements
- • size: Size of each element
- • Bonus: Initializes all bytes to ZERO!
| Feature | malloc() | calloc() |
|---|---|---|
| Arguments | malloc(total_bytes) | calloc(count, size) |
| Initialization | Garbage values | All zeros |
| Speed | Slightly faster | Slightly slower |
1#include <stdio.h>2#include <stdlib.h>34int main() {5 int *ptr = (int *)calloc(5, sizeof(int));6 7 if (ptr == NULL) {8 printf("Allocation Failed!\n");9 return 1;10 }11 12 // No need to initialize - already 0!13 for (int i = 0; i < 5; i++)14 printf("%d ", ptr[i]);15 16 free(ptr);17 return 0;18}0 0 0 0 0
How calloc() works:
int *ptr = (int *)calloc(5, sizeof(int));
ptr points to:
All initialized to zero!
💡 When to Use calloc?
Use calloc() when you need zero-initialized memory (like arrays, counters, or structures). Use malloc()when you'll immediately overwrite all values anyway.
05realloc() - Resize Memory
🔄 The Resize Problem
You allocated space for 5 students, but now you have 10! realloc() lets you grow or shrink existing memory without losing the original data.
📝 Syntax
void* realloc(void *ptr, size_t new_size);
- • ptr: Pointer to previously allocated memory
- • new_size: New size in bytes
- • Returns: Pointer to resized memory (may be different address!)
Basic realloc Example
1#include <stdio.h>2#include <stdlib.h>34int main() {5 // Start with 5 integers6 int *ptr = (int *)malloc(5 * sizeof(int));7 8 // Resize to hold 10 integers9 ptr = (int *)realloc(ptr, 10 * sizeof(int));10 11 if (ptr == NULL) {12 printf("Reallocation Failed!\n");13 return 1;14 }15 16 printf("Successfully resized to 10 integers\n");17 free(ptr);18 return 0;19}How realloc() works:
Before: 5 integers
After: 10 integers (old data preserved)
New slots are uninitialized
⚠️ Critical: realloc Can Fail!
If realloc() fails and returns NULL, the original memory is NOT freed. If you overwrite the original pointer, you lose access to that memory = memory leak!
✅ Safe realloc Pattern (Recommended)
Use a temp pointer to avoid memory leaks on failure:
1#include <stdio.h>2#include <stdlib.h>34int main() {5 int *ptr = (int *)malloc(5 * sizeof(int));6 7 // Use temp pointer for safety!8 int *temp = (int *)realloc(ptr, 10 * sizeof(int));9 10 if (temp == NULL) {11 printf("Reallocation Failed!\n");12 // ptr is still valid, can still use or free it13 free(ptr);14 return 1;15 }16 17 // Only update ptr after success18 ptr = temp;19 20 printf("Success!\n");21 free(ptr);22 return 0;23}Grow and Shrink Example
1#include <stdio.h>2#include <stdlib.h>34int main() {5 // Start with 5 integers6 int *ptr = (int *)malloc(5 * sizeof(int));7 for (int i = 0; i < 5; i++)8 ptr[i] = (i + 1) * 10;9 10 // Grow to 8 integers11 ptr = (int *)realloc(ptr, 8 * sizeof(int));12 13 // Shrink back to 5 integers14 ptr = (int *)realloc(ptr, 5 * sizeof(int));15 16 for (int i = 0; i < 5; i++)17 printf("%d ", ptr[i]);18 19 free(ptr);20 return 0;21}10 20 30 40 50
06free() - Release Memory
🚨 Critical Rule
Memory allocated using malloc() and calloc()is NOT automatically deallocated. You MUST use free()to release it back to the operating system!
📝 Syntax
void free(void *ptr);
- • ptr: Pointer returned by malloc/calloc/realloc
- • Passing NULL to free() is safe (does nothing)
- • Never free the same pointer twice!
1#include <stdio.h>2#include <stdlib.h>34int main() {5 int *ptr = (int *)calloc(5, sizeof(int));6 7 // Do some operations...8 for (int i = 0; i < 5; i++)9 printf("%d ", ptr[i]);10 11 // Free memory when done12 free(ptr);13 14 // Good practice: set to NULL15 ptr = NULL;16 17 return 0;18}0 0 0 0 0
How free() works:
Before free(): ptr points to valid memory
After free(): pointer is INVALID (dangling)
After ptr = NULL: Safe, no dangling pointer
💡 Best Practice: Set to NULL After free()
After free(ptr), the pointer still holds the old address (called a dangling pointer). Set it to NULLto prevent accidental use and make debugging easier.
07Accessing Dynamic Memory
📝 Key Insight
Dynamic memory behaves like an array! You can access elements using index notation ptr[i] or pointer dereferencing *ptr.
1#include <stdio.h>2#include <stdlib.h>34int main() {5 int *ptr = calloc(4, sizeof(int));6 7 // Write using array notation8 ptr[0] = 10;9 ptr[1] = 20;10 ptr[2] = 30;11 12 // Write using dereference (same as ptr[0])13 *ptr = 5; // Changes ptr[0] to 514 15 // Read values16 printf("%d %d %d %d\n", ptr[0], ptr[1], ptr[2], ptr[3]);17 18 free(ptr);19 return 0;20}5 20 30 0
| Notation | Meaning | Example |
|---|---|---|
| ptr[0] | First element | ptr[0] = 10; |
| *ptr | First element (same as ptr[0]) | *ptr = 10; |
| ptr[i] | Element at index i | ptr[3] = 40; |
| *(ptr + i) | Same as ptr[i] | *(ptr + 3) = 40; |
💡 sizeof() Doesn't Work on Dynamic Memory!
sizeof(ptr) returns the size of the pointer (8 bytes), NOT the allocated memory size. You must track the size yourself!
08Memory Leaks
💧 What is a Memory Leak?
A memory leak happens when you allocate memory but never free it. The memory remains "occupied" but unreachable. Over time, your program uses more and more memory until it crashes!
❌ Example: Memory Leak
1#include <stdlib.h>23void leak_example() {4 int *p = malloc(100 * sizeof(int));5 // Oops! Function ends without free()6 // Memory is lost forever!7}89int main() {10 for (int i = 0; i < 1000; i++) {11 leak_example(); // 400 bytes leaked each time!12 }13 // 400 KB leaked in total!14 return 0;15}✅ Fixed: No Memory Leak
1#include <stdlib.h>23void no_leak() {4 int *p = malloc(100 * sizeof(int));5 // ... use the memory ...6 free(p); // Always free before returning!7}89int main() {10 for (int i = 0; i < 1000; i++) {11 no_leak(); // Memory freed each time12 }13 // No leak!14 return 0;15}🔍 3 Ways to Lose a Pointer (and Leak Memory)
❌ Case 1: Pointer is Overwritten
1int x = 5;2int *ptr;3ptr = calloc(2, sizeof(*ptr));4ptr = &x; // LEAK! Original memory lost!After ptr = &x, the memory from calloc() can no longer be accessed or freed.
❌ Case 2: Pointer Only Exists in Function
1void myFunction() {2 int *ptr = malloc(sizeof(*ptr));3 // Function ends, ptr is gone!4 // Memory still allocated, but unreachable5}67int main() {8 myFunction(); // LEAK!9 return 0;10}Fix: Either free(ptr) before returning, or return the pointer.
❌ Case 3: realloc() Fails and Overwrites Pointer
1int *ptr = malloc(sizeof(*ptr));23// DANGEROUS! If realloc fails, ptr becomes NULL4ptr = realloc(ptr, 2 * sizeof(*ptr));5// Original memory is now unreachable!Fix: Use a temp pointer: temp = realloc(ptr, ...); if(temp) ptr = temp;
✅ Best Practices Summary
- 1.Always check for NULL after allocation
- 2.Always free() memory when no longer needed
- 3.Set pointer to NULL after free()
- 4.Use temp pointer with realloc()
09Practical Example: Dynamic Array
1#include <stdio.h>2#include <stdlib.h>34int main() {5 int n;6 printf("How many numbers? ");7 scanf("%d", &n);8 9 // Allocate array based on user input10 int *nums = malloc(n * sizeof(int));11 if (nums == NULL) {12 printf("Allocation failed!\n");13 return 1;14 }15 16 // Read numbers17 printf("Enter %d numbers:\n", n);18 for (int i = 0; i < n; i++) {19 scanf("%d", &nums[i]);20 }21 22 // Calculate sum23 int sum = 0;24 for (int i = 0; i < n; i++) {25 sum += nums[i];26 }27 28 printf("Sum: %d\n", sum);29 30 free(nums);31 return 0;32}How many numbers? 3
Enter 3 numbers:
10 20 30
Sum: 60
10Issues with Dynamic Memory
Dynamic memory is powerful but error-prone. Careful handling is required to avoid high memory usage or system crashes.
| Issue | Description | Prevention |
|---|---|---|
| Memory Leaks | Failing to free memory, exhausting system resources | Always call free() for every malloc/calloc |
| Dangling Pointers | Using pointer after free() causes undefined behavior | Set pointer to NULL after free() |
| Fragmentation | Repeated alloc/dealloc creates unusable memory gaps | Allocate larger blocks, reuse memory |
| Allocation Failures | If malloc fails and returns NULL, program may crash | Always check for NULL before using |
| Double Free | Calling free() twice on same pointer causes crash | Set to NULL after free() |
11Common Mistakes
❌ Not Checking for NULL
int *p = malloc(1000);
*p = 5; // Crash if NULL!
int *p = malloc(1000);
if (p != NULL) *p = 5;
❌ Using Memory After free()
free(p);
*p = 10; // Undefined behavior!
❌ Double free()
free(p);
free(p); // Crash! Double free
❌ Freeing Non-heap Memory
int arr[10];
free(arr); // Crash! Not from malloc
❌ Wrong Size Calculation
int *p = malloc(5);
int *p = malloc(5 * sizeof(int));
12Summary
🎯 Key Takeaways
- •malloc(size): Allocate bytes (uninitialized)
- •calloc(n, size): Allocate n elements (zeroed)
- •realloc(ptr, size): Resize existing memory
- •free(ptr): Release memory back to system
- •Always: Check for NULL, free when done, set ptr = NULL
int *p = malloc(n * sizeof(int));
if (p == NULL) { return 1; }
free(p);
p = NULL;
10Next Steps
Learn to combine structures with dynamic memory: