Skip to main content

Advanced Topics

Dynamic Memory

Master C Dynamic Memory — static vs dynamic memory, malloc / calloc / realloc / free, malloc vs calloc, and dynamic arrays — using LEARNCODEGURU’s 6-level learning model.

Level 1 Basics Content

C Dynamic Memory Overview

Memory in a C program is obtained in two ways. Static (compile-time) allocation fixes the size before the program runs. Dynamic (run-time) allocation asks the operating system for memory while the program is running. This level covers static memory, dynamic memory, a static vs dynamic comparison, implementing dynamic memory in C, malloc vs calloc, and dynamic arrays.

Four library functions do all the work, all declared in <stdlib.h>: malloc, calloc, realloc, and free.

Dynamic memory in C: a pointer variable on the stack holding the address of a block allocated on the heap by malloc or calloc, which is resized with realloc and released with free.
A pointer holds the address of a heap block obtained at run time with malloc or calloc
Topic 1

What is static memory?

Static memory allocation means the amount of memory is decided at compile time and cannot change while the program runs. Ordinary variables, fixed-size arrays, and globals all work this way.


int x;              /* one int — size fixed at compile time */
int a[100];         /* always 100 ints, even if you use 3 */
char name[50];      /* always 50 bytes */
  • Size known before running — the compiler reserves it
  • Automatic cleanup — locals disappear when the block ends
  • Lives on the stack (locals) or in the data segment (globals/statics)
  • Cannot resize — a[100] can never become a[200]
The problem: if you declare int a[100] and the user needs 500 values, your program fails. If they need 3, you wasted 97 slots. Static sizing forces you to guess.
Topic 2

What is dynamic memory?

Dynamic memory allocation means requesting memory at run time, after you know how much you actually need. The memory comes from a region called the heap and stays yours until you explicitly release it.


#include <stdlib.h>

int n;
scanf("%d", &n);              /* size decided by the user */

int *a = malloc(n * sizeof(*a));
if (a == NULL) return 1;      /* allocation can fail */

/* ... use a[0] .. a[n-1] ... */

free(a);                       /* you must release it */
a = NULL;
  • Size decided while running — exactly what you need
  • Lives on the heap — survives until free
  • Accessed only through a pointer
  • Manual cleanup — forgetting free causes a memory leak
  • Can be resized with realloc
Decide size at run time → allocate → check for NULL → use → free → set pointer to NULL
Topic 3

Static vs dynamic memory

# Aspect Static allocation Dynamic allocation
1 When size is decided Compile time Run time
2 Memory region Stack / data segment Heap
3 Declared using int a[100]; malloc / calloc / realloc
4 Can be resized? No Yes, with realloc
5 Deallocation Automatic (scope end) Manual — you call free
6 Lifetime Block or whole program Until free is called
7 Access Variable name or index Only through a pointer
8 Speed Faster (simple stack move) Slower (heap bookkeeping)
9 Memory efficiency May waste or fall short Allocate exactly what is needed
10 Typical risk Fixed limit / stack overflow Memory leaks, dangling pointers
11 Can outlive its function? No (locals die on return) Yes — return the pointer safely
Use static memory when the size is small and known. Use dynamic memory when the size depends on input, may grow, or must outlive the function that created it.
Topic 4

Implementing dynamic memory in C

All four functions live in <stdlib.h>:

# Function Prototype Purpose Returns
1 malloc void *malloc(size_t size); Allocate one block of size bytes Pointer, or NULL on failure
2 calloc void *calloc(size_t n, size_t size); Allocate n items and zero them Pointer, or NULL on failure
3 realloc void *realloc(void *p, size_t size); Resize an existing block New pointer, or NULL on failure
4 free void free(void *p); Release a block back to the heap Nothing

malloc — allocate raw bytes:


int *p = malloc(5 * sizeof(*p));   /* room for 5 ints */
if (p == NULL) {
    printf("allocation failed\n");
    return 1;
}
/* contents are garbage — write before reading */

calloc — allocate and zero:


int *q = calloc(5, sizeof(*q));   /* 5 ints, all 0 */

realloc — grow or shrink:


int *tmp = realloc(p, 10 * sizeof(*p));
if (tmp != NULL)
    p = tmp;        /* never assign realloc directly to p */

free — release it:


free(p);
p = NULL;         /* avoid a dangling pointer */
Golden rule: every successful malloc / calloc / realloc needs exactly one matching free.
Topic 5

calloc() vs malloc()

# Aspect malloc() calloc()
1 Full form Memory allocation Contiguous allocation
2 Number of arguments One — total bytes Two — count and item size
3 Syntax malloc(n * sizeof(int)) calloc(n, sizeof(int))
4 Initialization None — garbage values All bytes set to 0
5 Speed Faster (no zeroing) Slightly slower (zeroing cost)
6 Safe to read before writing? No — undefined behavior Yes — reads as 0
7 Returns on failure NULL NULL
8 Released with free() free()
9 Best used when You will fill every element yourself Zero is the correct starting value

/* These two request the same amount of memory */
int *a = malloc(10 * sizeof(int));  /* values: garbage */
int *b = calloc(10, sizeof(int));  /* values: all 0   */
Choosing between them is about initialization, not capability. If you need counters or sums starting at zero, calloc saves you a loop.
Topic 6

Dynamic arrays — 1D

A dynamic array is just a pointer to a heap block treated as consecutive elements. You index it exactly like a normal array.


int n, i;
int *a;

printf("How many elements? ");
scanf("%d", &n);

a = malloc(n * sizeof(*a));
if (a == NULL) return 1;

for (i = 0; i < n; i++)
    scanf("%d", &a[i]);      /* a[i] works normally */

free(a);
a = NULL;

Growing it later with realloc:


int *tmp = realloc(a, (n * 2) * sizeof(*a));
if (tmp == NULL) {
    /* old block still valid — handle the error */
} else {
    a = tmp;
    n = n * 2;   /* existing values are preserved */
}
Note: sizeof(a) gives the pointer size, not the array size. You must track n yourself.
Topic 7

Dynamic arrays — 2D

Method A — array of row pointers (int **):


int **m;
int i, rows = 3, cols = 4;

m = malloc(rows * sizeof(*m));       /* array of row pointers */
for (i = 0; i < rows; i++)
    m[i] = malloc(cols * sizeof(**m));  /* each row */

m[1][2] = 99;                          /* normal 2D syntax */

/* free in reverse order: rows first, then the outer array */
for (i = 0; i < rows; i++)
    free(m[i]);
free(m);

Method B — one flat contiguous block:


int *m = malloc(rows * cols * sizeof(*m));

m[1 * cols + 2] = 99;   /* row 1, column 2 */

free(m);                     /* one allocation, one free */
Aspect Method A (int **) Method B (flat block)
Allocations rows + 1 1
Indexing m[i][j] m[i * cols + j]
Contiguous memory? No — rows scattered Yes
Freeing Loop, then outer free Single free
Rows of different lengths? Possible (jagged) No
Method B is usually better: fewer allocations, better cache behavior, and impossible to leak a row. Use Method A when rows genuinely need different lengths.
Example

Sample program — dynamic array with malloc and free


/* Dynamic memory basics: allocate, check, use, free */
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    int *a;
    int n, i, sum = 0;

    printf("How many integers? ");
    scanf("%d", &n);

    if (n <= 0) {
        printf("n must be positive\n");
        return 1;
    }

    a = malloc(n * sizeof(*a));
    if (a == NULL) {
        printf("Out of memory\n");
        return 1;
    }

    printf("Enter %d integers:\n", n);
    for (i = 0; i < n; i++) {
        scanf("%d", &a[i]);
        sum += a[i];
    }

    printf("Array: ");
    for (i = 0; i < n; i++)
        printf("%d ", a[i]);
    printf("\nSum = %d\n", sum);

    free(a);
    a = NULL;

    return 0;
}
What to observe: the size comes from the user, the return value is checked before use, indexing works like a normal array, and the block is freed exactly once.

Level 2 Advanced Content

Going deeper into dynamic memory

Level 1 covered static vs dynamic, the four functions, malloc vs calloc, and dynamic arrays. Level 2 explains memory segments, realloc in detail, memory leaks, dangling and double-free bugs, allocating strings and structs, returning heap memory from functions, and common mistakes.

Topic 1

Stack vs heap — where memory comes from

Aspect Stack Heap
Holds Locals, parameters, return addresses Blocks from malloc family
Managed by Compiler automatically You, explicitly
Allocation cost Very cheap (move a pointer) Higher (search / bookkeeping)
Size limit Small (often 1–8 MB) Large (most of available RAM)
Lifetime Until the block exits Until free
Failure mode Stack overflow malloc returns NULL, leaks
This is why large arrays should be heap-allocated: int a[1000000]; as a local may overflow the stack, while malloc handles it comfortably.
Topic 2

realloc in detail

  • Existing contents are preserved up to the smaller of the old and new sizes
  • When growing, the extra bytes are uninitialized (not zeroed)
  • The block may be moved — the old pointer becomes invalid
  • realloc(NULL, n) behaves like malloc(n)
  • On failure it returns NULL and the original block is untouched

/* WRONG — leaks the original block if realloc fails */
p = realloc(p, newSize);

/* RIGHT — keep the old pointer until success is confirmed */
int *tmp = realloc(p, newSize);
if (tmp == NULL) {
    /* p is still valid; recover or exit */
} else {
    p = tmp;
}
Topic 3

Memory leaks

A leak happens when you lose the last pointer to a block you never freed. The memory stays reserved for the life of the process.


int *p = malloc(100 * sizeof(*p));
p = malloc(200 * sizeof(*p));   /* LEAK: first block is unreachable */

/* Also a leak: returning early without freeing */
if (error)
    return 1;                    /* forgot free(p) */
  • Free before overwriting a pointer that owns memory
  • Free on every exit path, including error returns
  • In a loop, a small leak per iteration becomes a large one quickly
Topic 4

Dangling pointers, double free, and use-after-free

Bug What happens Fix
Dangling pointer p still holds a freed address p = NULL; right after free
Use-after-free Reading/writing freed memory — undefined Never touch p after freeing
Double free free(p) twice — undefined, often aborts Set to NULL; free(NULL) is safe
Freeing a non-heap pointer free on a stack array — undefined Only free what the malloc family returned
Freeing a shifted pointer free(p + 1) — undefined Free the exact original address

free(p);
p = NULL;     /* now free(p) again is harmless, and if (p) works */
Topic 5

Allocating strings and structs


/* String buffer: remember +1 for '\0' */
char *s = malloc((len + 1) * sizeof(*s));
if (s != NULL) {
    strcpy(s, "hello");
    free(s);
}

/* One struct */
struct Point { int x, y; };

struct Point *pt = malloc(sizeof(*pt));
pt->x = 3;
pt->y = 4;
free(pt);

/* Array of structs */
struct Point *pts = calloc(n, sizeof(*pts));
String rule: allocate strlen(text) + 1 bytes. Forgetting the terminator byte is the most common heap-string bug.
Topic 6

Returning heap memory from a function

Unlike a local array, heap memory outlives the function — so returning the pointer is safe. The caller then owns it and must free it.


/* SAFE — heap block survives the return */
int *makeArray(int n) {
    int *a = calloc(n, sizeof(*a));
    return a;    /* caller must free it */
}

/* UNSAFE — local dies at return, pointer dangles */
int *bad(void) {
    int a[10];
    return a;    /* undefined behavior */
}
Document ownership: if a function returns allocated memory, say so in a comment so the caller knows it is responsible for free.
Topic 7

Common mistakes

Mistake Why it hurts Fix
Not checking for NULL Crash when memory runs out Test the return value every time
Forgetting free Memory leak grows over time Pair each allocation with a free
Using memory after free Undefined behavior, silent corruption Set the pointer to NULL
Double free Heap corruption or abort NULL after freeing
Reading malloc memory before writing Garbage values / undefined Use calloc or initialize first
Wrong size — forgetting * n Buffer overflow past the block n * sizeof(*p)
Missing <stdlib.h> Implicit declaration bugs Always include it
p = realloc(p, n) directly Leaks the old block on failure Use a temporary pointer
Freeing only the outer pointer of int ** Every row leaks Free rows first, then the outer array

Level 3 C Tricky Interview Points

Advanced tricky interview points on malloc vs calloc, NULL checks, realloc semantics, leaks, dangling pointers, and dynamic 2D arrays.

Tricky Interview Point-1:

malloc does not initialize memory; calloc zeroes it

Memory from malloc holds indeterminate bytes — reading it before writing is undefined behavior. calloc sets every byte to 0, which is why it is slightly slower but safer for arrays you intend to accumulate into.

Tricky Interview Point-2:

Always check the return value — malloc can return NULL

On allocation failure malloc/calloc/realloc return NULL. Dereferencing that is a crash. Write if (p == NULL) { /* handle */ } immediately after every allocation.

Tricky Interview Point-3:

sizeof(*p) is safer than repeating the type name

int *p = malloc(n * sizeof(*p)); stays correct even if you later change p to long *. Writing sizeof(int) twice invites a mismatch when the type changes.

Tricky Interview Point-4:

Casting malloc's return is unnecessary in C

malloc returns void *, which converts implicitly to any object pointer type. The cast is required in C++ but in C it can hide a missing <stdlib.h> include, which used to cause real bugs.

Tricky Interview Point-5:

free(NULL) is safe and does nothing

The standard guarantees it, so you do not need if (p != NULL) free(p);. What is NOT safe is freeing the same pointer twice — double free is undefined behavior.

Tricky Interview Point-6:

free does not change your pointer — it becomes dangling

After free(p), p still holds the old address. Using it is use-after-free. Set p = NULL; immediately so later checks and accidental uses fail loudly instead of silently corrupting memory.

Tricky Interview Point-7:

You must free the exact pointer malloc returned

If you advance a pointer (p++ while walking an array), you cannot free the advanced value. Keep the original address in a separate variable to pass to free.

Tricky Interview Point-8:

realloc may move the block and invalidate the old pointer

Assign to a temporary: tmp = realloc(p, newsize); if (tmp) p = tmp;. Writing p = realloc(p, n) leaks the original block when realloc returns NULL.

Tricky Interview Point-9:

realloc(NULL, n) behaves like malloc(n); realloc(p, 0) is implementation-defined

The first form is a documented convenience. The second may free the block and return NULL, or return a unique pointer — do not rely on it; call free explicitly instead.

Tricky Interview Point-10:

Heap memory lives until you free it — not until the function returns

This is why a function can allocate a buffer and return the pointer safely, unlike returning the address of a local array. The trade-off is that someone must remember to free it.

Tricky Interview Point-11:

A memory leak is losing the last pointer to a live block

Overwriting p without freeing the old block leaks it. The memory stays reserved until the process exits. Long-running programs die from accumulated leaks.

Tricky Interview Point-12:

VLAs are stack-allocated, not dynamic memory

int a[n]; (C99) sizes at runtime but lives on the stack and vanishes at block exit — you cannot free it or return it. Large n risks stack overflow, which is why malloc is preferred for big data.

Tricky Interview Point-13:

A dynamic 2D array via int ** is not contiguous

An array of row pointers means rows can sit anywhere in the heap, so a single memcpy across the whole matrix is invalid. A single malloc(rows*cols*sizeof(int)) with manual indexing gives one contiguous block.

Tricky Interview Point-14:

Freeing a 2D int ** array requires a loop, in reverse order

Free each row first, then the array of pointers. Freeing the outer pointer first loses the row addresses and leaks every row.

Interview tip: for any allocation question, mention initialization, NULL checking, and who owns the free. Those three points cover most of what interviewers are listening for.

Level 4 Simple Programs

Short programs covering malloc, calloc comparison, realloc growth, dynamic strings, and dynamic 2D matrices.

Program 1 — malloc vs calloc side by side


/* calloc gives zeros; malloc gives garbage */
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    int n = 5, i;
    int *a = malloc(n * sizeof(*a));
    int *b = calloc(n, sizeof(*b));

    if (a == NULL || b == NULL) {
        printf("allocation failed\n");
        free(a);
        free(b);
        return 1;
    }

    printf("calloc values (guaranteed 0): ");
    for (i = 0; i < n; i++)
        printf("%d ", b[i]);
    printf("\n");

    /* malloc memory must be written before it is read */
    for (i = 0; i < n; i++)
        a[i] = i * 10;

    printf("malloc values (after writing): ");
    for (i = 0; i < n; i++)
        printf("%d ", a[i]);
    printf("\n");

    free(a);
    free(b);
    return 0;
}

Program 2 — Growing an array with realloc


/* Existing values survive the resize */
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    int n = 3, i;
    int *a = malloc(n * sizeof(*a));
    int *tmp;

    if (a == NULL) return 1;

    for (i = 0; i < n; i++)
        a[i] = i + 1;        /* 1 2 3 */

    tmp = realloc(a, 6 * sizeof(*a));
    if (tmp == NULL) {
        printf("resize failed\n");
        free(a);
        return 1;
    }
    a = tmp;

    for (i = 3; i < 6; i++)
        a[i] = (i + 1) * 10;  /* new slots */

    printf("After realloc: ");
    for (i = 0; i < 6; i++)
        printf("%d ", a[i]);
    printf("\n");

    free(a);
    return 0;
}

Program 3 — Dynamic 2D matrix with int **


/* Allocate rows, then each row; free in reverse */
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    int **m;
    int rows, cols, i, j;

    printf("Enter rows and cols: ");
    scanf("%d %d", &rows, &cols);

    m = malloc(rows * sizeof(*m));
    if (m == NULL) return 1;

    for (i = 0; i < rows; i++) {
        m[i] = malloc(cols * sizeof(**m));
        if (m[i] == NULL) return 1;
    }

    printf("Enter %d elements:\n", rows * cols);
    for (i = 0; i < rows; i++)
        for (j = 0; j < cols; j++)
            scanf("%d", &m[i][j]);

    printf("Matrix:\n");
    for (i = 0; i < rows; i++) {
        for (j = 0; j < cols; j++)
            printf("%4d", m[i][j]);
        printf("\n");
    }

    for (i = 0; i < rows; i++)
        free(m[i]);      /* rows first */
    free(m);              /* then the outer array */

    return 0;
}

Program 4 — Dynamic 2D matrix as one flat block


/* One malloc, one free, contiguous memory */
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    int *m;
    int rows = 2, cols = 3, i, j;

    m = calloc(rows * cols, sizeof(*m));
    if (m == NULL) return 1;

    for (i = 0; i < rows; i++)
        for (j = 0; j < cols; j++)
            m[i * cols + j] = (i + 1) * (j + 1);

    printf("Matrix:\n");
    for (i = 0; i < rows; i++) {
        for (j = 0; j < cols; j++)
            printf("%4d", m[i * cols + j]);
        printf("\n");
    }

    free(m);
    return 0;
}

Program 5 — Dynamic string buffer


/* Allocate a line buffer on the heap */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(void) {
    int cap = 100;
    char *line = malloc(cap * sizeof(*line));
    size_t len;

    if (line == NULL) return 1;

    printf("Enter a line: ");
    if (fgets(line, cap, stdin) == NULL) {
        free(line);
        return 1;
    }

    len = strlen(line);
    if (len > 0 && line[len - 1] == '\n')
        line[len - 1] = '\0';

    printf("You typed: [%s] length = %zu\n", line, strlen(line));

    free(line);
    line = NULL;
    return 0;
}
Comment Directive Type Keyword Function Variable String Number

Level 5 Problem Solving Tricks

How to master dynamic memory while solving problems

Most heap bugs come from unchecked allocations, forgotten frees, using memory after freeing, and wrong size calculations. These habits make dynamic memory routine instead of risky.

Trick 1

Pair every malloc with a free before you write more code

Allocate, then immediately scroll to where the block dies and write the free. Treating them as a matched pair prevents most leaks from ever being created.

Trick 2

Check for NULL on the very next line

p = malloc(...); if (p == NULL) { fprintf(stderr, "out of memory\n"); return 1; } Make it muscle memory so no allocation is ever used unchecked.

Trick 3

Use n * sizeof(*p) as your allocation formula

It reads as "n of whatever p points to" and survives type changes. Combine with a named count variable so the size is obvious to reviewers.

Trick 4

Set the pointer to NULL right after freeing

free(p); p = NULL; converts a silent use-after-free into an immediate, debuggable failure and makes if (p) checks meaningful later.

Trick 5

Keep the original pointer when traversing

Walk with a second pointer (int *q = p;) so p always holds the address free needs. Never increment the pointer you plan to release.

Trick 6

Use a temporary for realloc

tmp = realloc(p, newSize); if (tmp == NULL) { /* keep old p, handle error */ } else p = tmp;. This is the only leak-free way to grow a block.

Trick 7

Prefer calloc when zeros are the correct starting value

For counters, sums, or flags, calloc removes an entire initialization loop and eliminates the risk of reading uninitialized bytes.

Trick 8

For a dynamic matrix, consider one flat block

int *m = malloc(rows * cols * sizeof(*m)); then index m[i * cols + j]. One allocation, one free, contiguous memory, and no pointer-array bookkeeping.

Quick success checklist

  • Before run: <stdlib.h> included? NULL checked? size is n * sizeof(*p)?
  • Debug order: print n and the pointer → verify every index < n → confirm one free per allocation
  • Cleanup habit: free on every exit path → p = NULL after free → rows before outer array for int **
Level-5 goal: treat every allocation as a contract — check it, track its size, know who owns it, and free it exactly once.

Level 6 Practice Programs List

Programming questions only — focused on dynamic memory: malloc, calloc, realloc, free, dynamic 1D and 2D arrays, and safe cleanup.

  1. 1. Write a C program that allocates memory for one integer using malloc, stores a value, prints it, and frees the memory.
  2. 2. Write a C program that reads n from the user, allocates a dynamic array of n integers with malloc, reads the values, and prints their sum.
  3. 3. Write a C program that does the same task with calloc and prints the array before reading input to show that it starts as all zeros.
  4. 4. Write a C program that allocates an array of n integers, then uses realloc to grow it to 2n and fills the new positions.
  5. 5. Write a C program that allocates a dynamic string buffer with malloc, reads a line with fgets, and prints its length.
  6. 6. Write a C program that creates a dynamic 2D matrix using an array of row pointers (int **), reads it, prints it, and frees it correctly.
  7. 7. Write a C program that creates a dynamic 2D matrix as a single flat block and accesses elements using m[i * cols + j].
  8. 8. Write a C program that allocates memory, checks for NULL, and prints an error message if the allocation fails.
  9. 9. Write a C program that demonstrates a dangling pointer by freeing memory and then setting the pointer to NULL as the fix.
  10. 10. Write a C program with a function that allocates and returns an array of n integers, with main responsible for freeing it.