C Language track

Lesson 13 · Level 4: Deep Dive

Dynamic Memory

Heap memory management and dynamic data structures

Level 4: Deep Dive4 min readFree lesson

Until now, the size of the array was determined at compile time. But what if you need an array with the size of N received as input at runtime? It's time for Dynamic Memory Allocation.

Learning Objectives

  • Understand the difference between stack and heap.
  • Know the roles of malloc, calloc, realloc, and free.
  • Avoid memory leaks, double free, and dangling pointers.
  • Create a dynamic 1-dimensional/2-dimensional array.

Why do you need dynamic memory? — Stack and Heap

memory area
┌─────────────────────┐ ◄── High address
│ Stack ▼ │ Local variables, function call information
│ │ Automatic Allocate/Deallocate, fast, small (typically 1 to 8 MB)
   ├─────────────────────┤
│ ↕ (empty space) │
   ├─────────────────────┤
│ Heap ▲ │ with-malloc/free Direct management
│ │ Almost no size limitations
   ├─────────────────────┤
│ data/code area │
└─────────────────────┘ ◄── Low address

With just the stack:

  • Size must be known at compile time
  • Size limitations (large arrays will overflow the stack)
  • Automatically disappears when the function ends (cannot be taken out of the function)

Since the heap does not have the above restrictions, patterns such as N inputs and N slots, Function creates and returns are possible.

Key concepts

1) 4 functions (<stdlib.h>)

function Meaning
void *malloc(size_t n) allocate n bytes (initialize X)
void *calloc(size_t cnt, size_t sz) After allocating cnt*sz bytes, initialized to 0
void *realloc(void *p, size_t n) Reallocate existing block to n bytes
void free(void *p) off

2) Basic usage pattern — “Allocate, Check, Enable, Free”

int *arr = malloc(n * sizeof(int));   //1) Assignment
if (arr == NULL) { /*Failure handling*/ } // 2) Check

for (int i = 0; i < n; i++) arr[i] = i;   //3) Use

free(arr);                             //4) Release
arr = NULL;                            //(Optional) Anti-dangling

1 call to malloc = 1 call to free. Always a pair.

3) Safe use of realloc

int *tmp = realloc(arr, new_size * sizeof(int));
if (tmp == NULL) {
    /*Failure: arr is still valid, can be used as is*/
} else {
    arr = tmp;
}

You must receive the result as a temporary variable so that you do not lose the original in case of failure.

4) Dynamic two-dimensional array

int **mat = malloc(rows * sizeof(int *));
for (int i = 0; i < rows; i++)
    mat[i] = malloc(cols * sizeof(int));

Disable in reverse order:

for (int i = 0; i < rows; i++) free(mat[i]);
free(mat);

See as example

Example 1 — ex01_malloc.c: Dynamic array

int *arr = malloc((size_t)n * sizeof(int));
if (arr == NULL) { perror("malloc"); return 1; }

Input: 5

Enter array size: 10 20 30 40 50

Bottom line: The input N determines the size of the array. Not possible with a static array.

Example 2 — ex02_calloc.c:0 initialization

Execution result

Immediately after calloc: 0 0 0 0 0

Key point: malloc is a garbage value, calloc is initialized to 0. 0 If initialization is required, calloc.

Example 3 — ex03_realloc.c: Dynamic expansion

Add 10 units and double the capacity when full.

Execution result

(Capacity expansion: 8)
(Capacity expansion: 16)
Final arrangement: 100 200 300 400 500 600 700 800 900 1000

Core: Core patterns of dynamic data structures (variable-length vectors, queues, etc.).

Example 4 — ex04_2d.c: Dynamic two-dimensional array

Execution result

   1   2   3   4
   5   6   7   8
   9  10  11  12

Bottom line: int ** is a "pointer's pointer". Allocate twice, free twice.

See it from a different perspective — “Borrowing books from the library”

malloc = borrow a book from the library (go and borrow it when you need it)
free = Return the borrowed book (return it when you finish using it)

If you borrow it and don’t return it → memory leak
Return the same book twice → double free
Return the borrowed book and read it again → Dangling pointer (use-after-free)

Libraries (OS) don't have infinite books, so if leaks build up, you'll eventually run out of books to borrow.

Common Mistakes

  1. Leak: Forgetting to call free or missing it from the branch.
  2. Double free: free the same pointer twice → undefined behavior.
  3. Use after release: Dangling pointer.
  4. Size calculation mistake: Omitting * sizeof(int) like malloc(n).
  5. malloc result NULL not checked: If memory is insufficient, NULL is returned → If written as is, it will be a segfault.
  6. Receiving the realloc result with the same pointer: In case of failure, the original is lost.

Cleanup

  • The stack is automatic, fast, and small, and the heap is manual, flexible, and large.
  • Assigned to malloc/calloc/realloc, released to free.
  • Allocation and deallocation are always paired 1:1.
  • Receive the result of realloc as a temporary variable to prepare for failure.
  • Leakage, double release, and dangling are the three major memory accidents.

Try it yourself

cd src
gcc -std=c11 -Wall -o ex01 ex01_malloc.c  && echo 5 | ./ex01
gcc -std=c11 -Wall -o ex02 ex02_calloc.c  && ./ex02
gcc -std=c11 -Wall -o ex03 ex03_realloc.c && ./ex03
gcc -std=c11 -Wall -o ex04 ex04_2d.c      && ./ex04

Application:

  • Check if there are any leaks with valgrind ./ex01 (Linux).
  • Try changing it to receive input at ex04's row/column execution time.

Finish

You have completed this track.

Example files

The runnable files used in this lesson. Copy them into your own project and experiment.

src/ex01_malloc.cC
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    int n;
    printf("Enter array size:");
    scanf("%d", &n);

    int *arr = malloc((size_t)n * sizeof(int));
    if (arr == NULL) { perror("malloc"); return 1; }

    for (int i = 0; i < n; i++) arr[i] = (i + 1) * 10;

    for (int i = 0; i < n; i++) printf("%d ", arr[i]);
    putchar('\n');

    free(arr);
    return 0;
}
src/ex02_calloc.cC
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    size_t n = 5;
    int *arr = calloc(n, sizeof(int));   /*reset to 0*/
    if (!arr) { perror("calloc"); return 1; }

    printf("Immediately after calloc:");
    for (size_t i = 0; i < n; i++) printf("%d ", arr[i]);
    putchar('\n');

    free(arr);
    return 0;
}
src/ex03_realloc.cC
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    size_t cap = 4;
    size_t len = 0;
    int *arr = malloc(cap * sizeof(int));
    if (!arr) { perror("malloc"); return 1; }

    /*Expand as needed by adding 10*/
    for (int i = 0; i < 10; i++) {
        if (len == cap) {
            cap *= 2;
            int *tmp = realloc(arr, cap * sizeof(int));
            if (!tmp) { free(arr); perror("realloc"); return 1; }
            arr = tmp;
            printf("(Capacity expansion: %zu)\n", cap);
        }
        arr[len++] = (i + 1) * 100;
    }

    printf("Final arrangement:");
    for (size_t i = 0; i < len; i++) printf("%d ", arr[i]);
    putchar('\n');

    free(arr);
    return 0;
}
src/ex04_2d.cC
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    int rows = 3, cols = 4;

    /*Allocating row pointer array*/
    int **mat = malloc((size_t)rows * sizeof(int *));
    if (!mat) { perror("malloc"); return 1; }

    /*Assign each row*/
    for (int i = 0; i < rows; i++) {
        mat[i] = malloc((size_t)cols * sizeof(int));
        if (!mat[i]) { perror("malloc"); return 1; }
        for (int j = 0; j < cols; j++) mat[i][j] = i * cols + j + 1;
    }

    /*output*/
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) printf("%4d", mat[i][j]);
        putchar('\n');
    }

    /*Turn off reverse order*/
    for (int i = 0; i < rows; i++) free(mat[i]);
    free(mat);
    return 0;
}

Assignment

Problem 1

After receiving the array size N from the user and N integers, Print the maximum and minimum values. Memory is managed by malloc/free.

  • File name: hw01.c

Problem 2

Dynamically allocate a random N×M matrix and initialize it to 0. Change only the diagonal lines to 1 and print them out. (Similar effect to identity matrix, does not need to be N=M)

  • File name: hw02.c

Problem 3

Receives a string as input from the user and dynamically allocates and returns a copy of the string. Implement the char *my_strdup(const char *s) function and show an example of its use. (strlen/memcpy of <string.h> can be used)

  • File name: hw03.c
Show the reference solution

Try the assignment yourself first — comparing your attempt with a working solution is where most of the learning happens.

hw01.cC
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    int n;
    printf("Enter N:");
    scanf("%d", &n);
    if (n <= 0) { printf("N must be a positive number.\n"); return 1; }

    int *arr = malloc((size_t)n * sizeof(int));
    if (!arr) { perror("malloc"); return 1; }

    printf("Enter %d integers:", n);
    for (int i = 0; i < n; i++) scanf("%d", &arr[i]);

    int mx = arr[0], mn = arr[0];
    for (int i = 1; i < n; i++) {
        if (arr[i] > mx) mx = arr[i];
        if (arr[i] < mn) mn = arr[i];
    }
    printf("Maximum value: %d\nMinimum value: %d\n", mx, mn);

    free(arr);
    return 0;
}
hw02.cC
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    int n, m;
    printf("Row N, Column M:");
    scanf("%d %d", &n, &m);

    int **mat = calloc((size_t)n, sizeof(int *));
    if (!mat) { perror("calloc"); return 1; }
    for (int i = 0; i < n; i++) {
        mat[i] = calloc((size_t)m, sizeof(int));
        if (!mat[i]) { perror("calloc"); return 1; }
    }

    int len = (n < m) ? n : m;
    for (int i = 0; i < len; i++) mat[i][i] = 1;

    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) printf("%2d", mat[i][j]);
        putchar('\n');
    }

    for (int i = 0; i < n; i++) free(mat[i]);
    free(mat);
    return 0;
}
hw03.cC
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

char *my_strdup(const char *s) {
    size_t len = strlen(s);
    char *copy = malloc(len + 1);
    if (!copy) return NULL;
    memcpy(copy, s, len + 1);   /*Contains null characters*/
    return copy;
}

int main(void) {
    char buf[256];
    printf("Enter sentence:");
    if (fgets(buf, sizeof(buf), stdin) == NULL) return 1;
    buf[strcspn(buf, "\n")] = '\0';

    char *dup = my_strdup(buf);
    if (!dup) { perror("malloc"); return 1; }

    printf("Source: %s\n", buf);
    printf("Copy: %s\n", dup);

    free(dup);
    return 0;
}

Learn this lesson with a mentor

Work through the C Language track on your own, or join a guided cohort at our Pitampura campus and online.

Call NowEnroll Now