Chapter 23Advanced

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.

22 min readUpdated 2024-12-16
malloccallocreallocfreeheapmemory leakdynamic array

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.

static_memory.c
C
1#include <stdio.h>
2
3int main() {
4 // Static allocation: 20 students = 80 bytes
5 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}
Output

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.

dynamic_memory.c
C
1#include <stdio.h>
2#include <stdlib.h>
3
4int main() {
5 int numStudents = 12; // Actual count
6
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}
Output

Size: 48 bytes

FeatureStatic MemoryDynamic Memory
When allocatedCompile timeRuntime
SizeFixedCan change (realloc)
Memory locationStackHeap
AccessVariable namePointers only
DeallocationAutomaticManual (free)
Exampleint 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
FunctionPurposeHeader
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:

High Address
STACK ↓ (grows downward)
Local variables, function calls
↕ Free Space ↕
HEAP ↑ (grows upward)
Dynamic memory (malloc, calloc)
Global/Static Variables
Code (Text Segment)
Low Address
FeatureStackHeap
ManagementAutomaticManual (you manage)
SpeedVery FastSlower
SizeLimited (~1-8 MB)Large (GB+)
LifetimeUntil function endsUntil you free()
Use forLocal variablesLarge/dynamic data
stack_vs_heap.c
C
1#include <stdio.h>
2#include <stdlib.h>
3
4int main() {
5 // STACK: automatic, fixed size
6 int a = 10; // On stack
7 int arr[5] = {1,2,3,4,5}; // On stack
8
9 // HEAP: manual, dynamic size
10 int *p = malloc(sizeof(int)); // On heap
11 *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}
Output

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), or NULL if 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:

malloc_basic.c
C
1#include <stdio.h>
2#include <stdlib.h>
3
4int 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}
Output

1 2 3 4 5

Step 2: Using sizeof() (Better)

The size of int depends on the architecture. Use sizeof() for portable code:

malloc_sizeof.c
C
1#include <stdio.h>
2#include <stdlib.h>
3
4int main() {
5 // sizeof(int) = 4 bytes on most systems
6 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!

malloc_safe.c
C
1#include <stdio.h>
2#include <stdlib.h>
3
4int 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!
Featuremalloc()calloc()
Argumentsmalloc(total_bytes)calloc(count, size)
InitializationGarbage valuesAll zeros
SpeedSlightly fasterSlightly slower
calloc_example.c
C
1#include <stdio.h>
2#include <stdlib.h>
3
4int 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}
Output

0 0 0 0 0

How calloc() works:

int *ptr = (int *)calloc(5, sizeof(int));

ptr points to:

0
0
0
0
0

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

realloc_basic.c
C
1#include <stdio.h>
2#include <stdlib.h>
3
4int main() {
5 // Start with 5 integers
6 int *ptr = (int *)malloc(5 * sizeof(int));
7
8 // Resize to hold 10 integers
9 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

1
2
3
4
5
↓ realloc(ptr, 10 * sizeof(int)) ↓

After: 10 integers (old data preserved)

1
2
3
4
5
?
?
?
?
?

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:

realloc_safe.c
C
1#include <stdio.h>
2#include <stdlib.h>
3
4int 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 it
13 free(ptr);
14 return 1;
15 }
16
17 // Only update ptr after success
18 ptr = temp;
19
20 printf("Success!\n");
21 free(ptr);
22 return 0;
23}

Grow and Shrink Example

realloc_grow_shrink.c
C
1#include <stdio.h>
2#include <stdlib.h>
3
4int main() {
5 // Start with 5 integers
6 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 integers
11 ptr = (int *)realloc(ptr, 8 * sizeof(int));
12
13 // Shrink back to 5 integers
14 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}
Output

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!
free_example.c
C
1#include <stdio.h>
2#include <stdlib.h>
3
4int 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 done
12 free(ptr);
13
14 // Good practice: set to NULL
15 ptr = NULL;
16
17 return 0;
18}
Output

0 0 0 0 0

How free() works:

Before free(): ptr points to valid memory

ptr
10
20
30
↓ free(ptr) ↓

After free(): pointer is INVALID (dangling)

ptr
???
???
???
Memory released!
↓ ptr = NULL ↓

After ptr = NULL: Safe, no dangling pointer

ptr
NULL

💡 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.

access_memory.c
C
1#include <stdio.h>
2#include <stdlib.h>
3
4int main() {
5 int *ptr = calloc(4, sizeof(int));
6
7 // Write using array notation
8 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 5
14
15 // Read values
16 printf("%d %d %d %d\n", ptr[0], ptr[1], ptr[2], ptr[3]);
17
18 free(ptr);
19 return 0;
20}
Output

5 20 30 0

NotationMeaningExample
ptr[0]First elementptr[0] = 10;
*ptrFirst element (same as ptr[0])*ptr = 10;
ptr[i]Element at index iptr[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

memory_leak.c
C
1#include <stdlib.h>
2
3void leak_example() {
4 int *p = malloc(100 * sizeof(int));
5 // Oops! Function ends without free()
6 // Memory is lost forever!
7}
8
9int 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

no_leak.c
C
1#include <stdlib.h>
2
3void no_leak() {
4 int *p = malloc(100 * sizeof(int));
5 // ... use the memory ...
6 free(p); // Always free before returning!
7}
8
9int main() {
10 for (int i = 0; i < 1000; i++) {
11 no_leak(); // Memory freed each time
12 }
13 // No leak!
14 return 0;
15}

🔍 3 Ways to Lose a Pointer (and Leak Memory)

❌ Case 1: Pointer is Overwritten

leak_overwrite.c
C
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

leak_function.c
C
1void myFunction() {
2 int *ptr = malloc(sizeof(*ptr));
3 // Function ends, ptr is gone!
4 // Memory still allocated, but unreachable
5}
6
7int 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

leak_realloc.c
C
1int *ptr = malloc(sizeof(*ptr));
2
3// DANGEROUS! If realloc fails, ptr becomes NULL
4ptr = 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

dynamic_sum.c
C
1#include <stdio.h>
2#include <stdlib.h>
3
4int main() {
5 int n;
6 printf("How many numbers? ");
7 scanf("%d", &n);
8
9 // Allocate array based on user input
10 int *nums = malloc(n * sizeof(int));
11 if (nums == NULL) {
12 printf("Allocation failed!\n");
13 return 1;
14 }
15
16 // Read numbers
17 printf("Enter %d numbers:\n", n);
18 for (int i = 0; i < n; i++) {
19 scanf("%d", &nums[i]);
20 }
21
22 // Calculate sum
23 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}
Output

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.

IssueDescriptionPrevention
Memory LeaksFailing to free memory, exhausting system resourcesAlways call free() for every malloc/calloc
Dangling PointersUsing pointer after free() causes undefined behaviorSet pointer to NULL after free()
FragmentationRepeated alloc/dealloc creates unusable memory gapsAllocate larger blocks, reuse memory
Allocation FailuresIf malloc fails and returns NULL, program may crashAlways check for NULL before using
Double FreeCalling free() twice on same pointer causes crashSet 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: