The Problem malloc() Solves
When you write int marks[100]; you are telling the compiler: "reserve space for exactly 100 integers before the program even starts". But what if the user needs only 5? You wasted 95 slots. What if they need 200? You crash.
Dynamic memory allocation solves this. Instead of deciding size at compile time, you ask for memory at runtime — after the user has told you how much they need.
int marks[100]; /* Always 100 slots. User enters 5 → 95 wasted. User needs 200 → crash! */
int n; scanf("%d", &n); int *marks = malloc(n * sizeof(int)); /* Exactly n slots. No waste. No crash. */
Hotel analogy (your field): A fixed array is like building 100 hotel rooms before any guests arrive. malloc() is like building a room only when a guest books — exactly as many as you need, no more, no less.
Stack vs Heap — Where Memory Lives
Your program's memory is divided into regions. You need to know two of them:
- Stack — where normal variables live. Fast. Automatically managed. Limited size. Disappears when function returns.
- Heap — where
malloc()allocates. Larger. You control it. Stays alive until you callfree().
Program memory layout — bottom to top
| Feature | Stack (normal variables) | Heap (malloc) |
|---|---|---|
| Where declared | Inside functions normally | Anywhere via malloc() |
| Size limit | ~1–8 MB (small) | Limited only by RAM (GBs) |
| When freed | Automatically when function returns | Only when YOU call free() |
| Speed | Very fast | Slightly slower |
| Risk | Stack overflow if too large | Memory leak if you forget free() |
| Example | int arr[100]; | malloc(100 * sizeof(int)); |
malloc() — Allocate Raw Memory
malloc stands for Memory ALLOCation. You tell it how many bytes you need. It finds that space on the heap and gives you back the starting address. You store that address in a pointer.
The memory it gives you is uninitialised — it contains random garbage values until you write to it.
pointer = (data_type *) malloc( number_of_bytes ); /* Always use sizeof() — never hardcode byte counts */ int *p = (int *) malloc( sizeof(int) ); /* 1 int */ float *f = (float *) malloc( sizeof(float) ); /* 1 float */ int *a = (int *) malloc( 5 * sizeof(int) ); /* 5 ints */ char *s = (char *) malloc( 50 * sizeof(char) ); /* 50 chars */
How malloc(sizeof(int)) works step by step
#include <stdio.h> #include <stdlib.h> /* malloc and free live here */ int main() { /* Allocate memory for one integer on the heap */ int *p = (int *) malloc(sizeof(int)); /* ALWAYS check — malloc returns NULL if it fails */ if (p == NULL) { printf("Memory allocation failed!\n"); return 1; } *p = 500; /* store value in heap memory */ printf("Value = %d\n", *p); printf("Address = %p\n", p); free(p); /* ALWAYS free when done — return memory to OS */ p = NULL; /* good habit — avoid using freed pointer */ return 0; }
Value = 500 Address = 0x55a3f2c (example heap address)
malloc() returns NULL. Dereferencing NULL crashes the program. One if (p == NULL) check prevents this.Dynamic Array — User Decides the Size
The most common use of malloc() is creating an array whose size is decided by the user at runtime. You allocate n * sizeof(type) bytes — enough room for n elements — and use the pointer exactly like a normal array.
#include <stdio.h> #include <stdlib.h> int main() { int n, i; int *marks; printf("How many students? "); scanf("%d", &n); /* Allocate exactly n integers — decided at runtime */ marks = (int *) malloc(n * sizeof(int)); if (marks == NULL) { printf("Not enough memory!\n"); return 1; } /* Use marks[] exactly like a normal array */ printf("Enter %d marks:\n", n); for (i = 0; i < n; i++) { printf(" Student %d: ", i + 1); scanf("%d", &marks[i]); } /* Compute total and average */ int total = 0; for (i = 0; i < n; i++) total += marks[i]; printf("Total : %d\n", total); printf("Average : %.1f\n", (float)total / n); free(marks); /* release heap memory */ marks = NULL; return 0; }
How many students? 3 Enter 3 marks: Student 1: 85 Student 2: 92 Student 3: 78 Total : 255 Average : 85.0
marks[i] works exactly like a normal array. Once you have the pointer, marks[0], marks[1], etc. access elements just like a regular array. The pointer and the array notation are the same thing — marks[i] is *(marks + i).calloc() — Allocate and Zero-Fill
calloc stands for Contiguous ALLOCation. It does the same job as malloc() — allocates heap memory — but with two differences:
- Different syntax — takes two arguments: number of elements and size of each
- Zero-fills — every byte is set to 0 automatically. malloc() leaves garbage, calloc() gives you clean zeros.
/* malloc — one argument — total bytes — garbage values */ int *a = (int *) malloc( 5 * sizeof(int) ); /* calloc — two arguments — count, size — zeros everything */ int *b = (int *) calloc( 5, sizeof(int) ); /* Both give you space for 5 integers. calloc initialises all to 0. malloc leaves random values. */
#include <stdio.h> #include <stdlib.h> int main() { int i; /* malloc — values are garbage (unknown) */ int *a = (int *) malloc(5 * sizeof(int)); printf("malloc (garbage): "); for (i = 0; i < 5; i++) printf("%d ", a[i]); /* calloc — values are always 0 */ int *b = (int *) calloc(5, sizeof(int)); printf("\ncalloc (zeros): "); for (i = 0; i < 5; i++) printf("%d ", b[i]); printf("\n"); free(a); free(b); return 0; }
malloc (garbage): 13248 0 -274829 1024 88 (random junk) calloc (zeros): 0 0 0 0 0
malloc() when you will fill all values yourself right away (no need to zero first — faster).realloc() — Resize Already Allocated Memory
After allocating memory with malloc(), you might need more space. realloc() resizes an existing allocation — it tries to extend it in place, or moves it somewhere larger if needed. The data already stored is preserved.
pointer = (data_type *) realloc( old_pointer, new_size_in_bytes ); int *p = (int *) malloc(3 * sizeof(int)); /* 3 ints */ p = (int *) realloc(p, 6 * sizeof(int)); /* grow to 6 ints */ /* original 3 values are still there */
#include <stdio.h> #include <stdlib.h> int main() { int i; int *p; /* Start with 3 integers */ p = (int *) malloc(3 * sizeof(int)); p[0] = 10; p[1] = 20; p[2] = 30; printf("Before realloc: "); for (i = 0; i < 3; i++) printf("%d ", p[i]); /* Grow to 6 integers — original values preserved */ p = (int *) realloc(p, 6 * sizeof(int)); if (p == NULL) { printf("realloc failed!\n"); return 1; } p[3] = 40; p[4] = 50; p[5] = 60; printf("\nAfter realloc: "); for (i = 0; i < 6; i++) printf("%d ", p[i]); printf("\n"); free(p); return 0; }
Before realloc: 10 20 30 After realloc: 10 20 30 40 50 60
free() and Memory Leaks — The Rules
Every call to malloc() or calloc() must be matched with a call to free(). If you allocate memory and never free it, it stays reserved for your program but unusable — this is called a memory leak.
A small program leaking a few bytes doesn't matter. But a server program that runs 24/7 and leaks memory every request will eventually use all the RAM and crash the entire system.
int *p = malloc(sizeof(int)); *p = 42; /* forgot free(p) — memory gone */ return 0; /* leak! */
int *p = malloc(sizeof(int)); *p = 42; free(p); /* ← matched */ p = NULL; /* safe habit */ return 0;
Four rules to always follow:
- Rule 1 — Always check
if (ptr == NULL)right after malloc. Never skip this. - Rule 2 — Every
malloc()orcalloc()must have exactly onefree(). - Rule 3 — After
free(ptr), immediately setptr = NULL. Using a freed pointer is undefined behaviour. - Rule 4 — Never
free()the same pointer twice — double-free causes a crash.
free(p) releases the memory. If you then write *p = 5, you are writing into memory that now belongs to someone else. This causes random crashes that are very hard to debug.Complete Program — All Four Functions Together
#include <stdio.h> #include <stdlib.h> int main() { int i, n; int *p; printf("Enter number of students: "); scanf("%d", &n); /* ── malloc: allocate for n students ────────────── */ p = (int *) malloc(n * sizeof(int)); if (p == NULL) { printf("malloc failed!\n"); return 1; } printf("malloc: %d slots allocated.\n", n); /* Fill with marks */ for (i = 0; i < n; i++) p[i] = (50 + i * 5); printf("Values: "); for (i = 0; i < n; i++) printf("%d ", p[i]); printf("\n"); /* ── realloc: add 3 more students ───────────────── */ p = (int *) realloc(p, (n + 3) * sizeof(int)); if (p == NULL) { printf("realloc failed!\n"); return 1; } printf("realloc: grown to %d slots.\n", n + 3); p[n] = 95; p[n+1] = 88; p[n+2] = 72; n += 3; /* ── calloc: a separate zeroed score array ──────── */ int *bonus = (int *) calloc(n, sizeof(int)); printf("calloc bonus array (all zeros): "); for (i = 0; i < n; i++) printf("%d ", bonus[i]); printf("\n"); /* Final marks + bonus */ printf("Final marks: "); for (i = 0; i < n; i++) printf("%d ", p[i]); printf("\n"); /* ── free: both allocations ─────────────────────── */ free(p); p = NULL; free(bonus); bonus = NULL; printf("Memory freed. Done.\n"); return 0; }
Enter number of students: 4 malloc: 4 slots allocated. Values: 50 55 60 65 realloc: grown to 7 slots. calloc bonus array (all zeros): 0 0 0 0 0 0 0 Final marks: 50 55 60 65 95 88 72 Memory freed. Done.
Quick Reference — All Four Functions
| Function | Purpose | Syntax | Initial values |
|---|---|---|---|
| malloc() | Allocate raw memory | malloc(n * sizeof(type)) |
Garbage (random) |
| calloc() | Allocate + zero-fill | calloc(n, sizeof(type)) |
All zeros |
| realloc() | Resize existing allocation | realloc(ptr, new_size) |
Old data preserved |
| free() | Release heap memory | free(ptr) |
Memory returned to OS |
| Header needed | Rule |
|---|---|
| #include <stdlib.h> | Required for all four functions |
| NULL check | Always check if (ptr == NULL) after malloc/calloc/realloc |
| Matching free | Every malloc must have exactly one free |
| After free | Set ptr = NULL immediately after free(ptr) |
| sizeof() | Always use sizeof() — never hardcode byte counts |
Quick Quiz
What does malloc(5 * sizeof(int)) do?
What is the difference between malloc() and calloc()?
What is a memory leak?
Why must you check if (ptr == NULL) after malloc?
What does realloc(p, 10 * sizeof(int)) do if p already points to 5 ints?
Lesson Checklist
- Fixed arrays are decided at compile time — dynamic memory is decided at runtime
- Stack = automatic, small, fast. Heap = manual, large, you manage it.
- malloc(n * sizeof(type)) — allocates n elements — returns address — values are garbage
- calloc(n, sizeof(type)) — same but sets all bytes to 0
- realloc(ptr, new_size) — resizes — old data preserved
- Always check if (ptr == NULL) after malloc/calloc/realloc
- Every malloc must be matched with exactly one free()
- After free(p), set p = NULL immediately
- Memory leak = allocate but never free — program slowly consumes all RAM
- I completed the quiz