Dynamic Memory Management

Dynamic memory is like renting an apartment — you apply when you need it (malloc), and you move out when you're done (free). If you never move out, you keep hogging resources — that's a memory leak.

Why Dynamic Memory

Local variables on the stack disappear when a function returns, and array sizes must be determined at compile time. When the data size is only known at runtime, or you need data to persist across function calls, you need dynamic memory on the heap.

C
int n;
scanf("%d", &n);
int arr[n];
⚠️ Note: Variable-length arrays (VLA) are a C99 feature not supported by all compilers, and are not recommended for large arrays. The proper approach is to use malloc.

malloc and free

malloc allocates a specified number of bytes on the heap and returns void *. You must free the memory when done.

C
int *p = (int *)malloc(sizeof(int) * 5);
if (p == NULL) {
    printf("Memory allocation failed\n");
    return 1;
}
for (int i = 0; i < 5; i++) {
    p[i] = i * 10;
}
for (int i = 0; i < 5; i++) {
    printf("%d ", p[i]);
}
free(p);
p = NULL;
TEXT
0 10 20 30 40
💡 Tip: malloc does not initialize memory — the contents are garbage values. Always check the return value for NULL after allocation. Setting the pointer to NULL after free is good practice to prevent dangling pointers.

calloc

calloc allocates memory and initializes every bit to 0. Its parameters are the number of elements and the size of each element.

C
int *p = (int *)calloc(5, sizeof(int));
if (p) {
    for (int i = 0; i < 5; i++) {
        printf("%d ", p[i]);
    }
    free(p);
}
TEXT
0 0 0 0 0

malloc vs calloc:

Function Initialization Parameters
malloc No initialization Total bytes
calloc Zeroed Element count, size of each element

realloc

realloc resizes previously allocated memory — it can expand or shrink.

C
int *p = (int *)malloc(sizeof(int) * 3);
p[0] = 10; p[1] = 20; p[2] = 30;

int *tmp = (int *)realloc(p, sizeof(int) * 5);
if (tmp) {
    p = tmp;
    p[3] = 40;
    p[4] = 50;
    for (int i = 0; i < 5; i++) {
        printf("%d ", p[i]);
    }
    free(p);
}
TEXT
10 20 30 40 50
⚠️ Note: realloc may return a new address (original data is automatically copied) or the same address. Never write p = realloc(p, ...) — if it fails and returns NULL, the original pointer is lost. Use a temporary variable to receive the result.

Dynamic 1D Array

Example

C
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    int n;
    printf("Enter number of elements: ");
    scanf("%d", &n);

    int *arr = (int *)calloc(n, sizeof(int));
    if (!arr) return 1;

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

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

    free(arr);
    arr = NULL;
    return 0;
}
▶ Try it Yourself
TEXT
Enter number of elements: 5
1 4 9 16 25

Dynamic 2D Array

There are two common approaches to implementing dynamic 2D arrays.

Approach 1: Pointer Array (Each Row Allocated Independently)

C
int rows = 3, cols = 4;
int matrix = (int )malloc(sizeof(int *) * rows);
for (int i = 0; i < rows; i++) {
    matrix[i] = (int *)calloc(cols, sizeof(int));
}

matrix[1][2] = 99;
printf("%d\n", matrix[1][2]);

for (int i = 0; i < rows; i++) {
    free(matrix[i]);
}
free(matrix);
TEXT
99

Approach 2: Contiguous Memory (Single Allocation)

C
int rows = 3, cols = 4;
int *buf = (int *)calloc(rows * cols, sizeof(int));
int matrix = (int )malloc(sizeof(int *) * rows);
for (int i = 0; i < rows; i++) {
    matrix[i] = buf + i * cols;
}

matrix[2][3] = 77;
printf("%d\n", matrix[2][3]);

free(matrix);
free(buf);
TEXT
77
💡 Tip: Approach 2 has contiguous memory, which is cache-friendly and simpler to free. Approach 1 allows each row to have a different number of columns (jagged array).

Example

C
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    int rows = 3, cols = 4;
    int m = (int )malloc(sizeof(int *) * rows);
    int *buf = (int *)calloc(rows * cols, sizeof(int));
    for (int i = 0; i < rows; i++) {
        m[i] = buf + i * cols;
    }

    int val = 1;
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            m[i][j] = val++;
        }
    }

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

    free(m);
    free(buf);
    return 0;
}
▶ Try it Yourself
TEXT
  1  2  3  4
  5  6  7  8
  9 10 11 12

Common Dynamic Memory Errors

Forgetting to free — Memory Leak

C
void leak(void) {
    int *p = (int *)malloc(sizeof(int) * 100);
}

When the function ends, the local variable p is gone, but the 100 ints of heap memory are still occupied and can never be freed.

Double free

C
int *p = (int *)malloc(sizeof(int));
free(p);
free(p);
⚠️ Note: Calling free on the same memory twice is undefined behavior and may crash the program. Set the pointer to NULL immediately after free — calling free(NULL) is safe.

Using Freed Memory

C
int *p = (int *)malloc(sizeof(int));
*p = 42;
free(p);
printf("%d\n", *p);
⚠️ Note: Accessing memory after freeing it is a dangling pointer — the results are unpredictable.

Out-of-Bounds Access

C
int *p = (int *)malloc(sizeof(int) * 5);
p[5] = 100;

5 elements were allocated with indices 0-4, so p[5] is out of bounds.

valgrind Introduction

Valgrind is a memory-checking tool on Linux that can detect memory leaks, out-of-bounds access, and uninitialized reads.

BASH
gcc -g -o myapp myapp.c
valgrind --leak-check=full ./myapp

Typical output:

TEXT
==12345== HEAP SUMMARY:
==12345==     in use at exit: 400 bytes in 1 blocks
==12345==   total heap usage: 2 allocs, 1 frees, 800 bytes allocated
==12345==
==12345== 400 bytes in 1 blocks are definitely lost in loss record 1 of 1
==12345==    at 0x4C2FB0F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==12345==    by 0x10915E: leak (myapp.c:5)
💡 Tip: On Windows you can use Dr. Memory or Visual Studio's debug heap. On macOS, use Instruments' Leaks tool.

❓ FAQ

Q What values are in the memory returned by malloc?
A Undefined — it's leftover garbage data from previous use. Use calloc if you need zeroed memory, or manually call memset.
Q Does a pointer still have a value after free?
A free only releases the memory — it doesn't change the pointer's value. It still holds that address (dangling pointer). That's why you should set it to NULL immediately after freeing.
Q Will realloc lose the original data when expanding?
A No. realloc guarantees that the original data is preserved (at least min(old_size, new_size) bytes). If it moves to a new address, it copies automatically.
Q Can I free a stack variable?
A Absolutely not. free can only release heap memory returned by malloc/calloc/realloc. Calling free on a stack address is undefined behavior.

📖 Summary

📝 Exercises

  1. Write a program: the user inputs n, dynamically allocate n ints, read n numbers, sort and output them, then free the memory
  2. Write dynamic 2D array functions: int create_matrix(int rows, int cols) and void free_matrix(int m, int rows). In main, create a 4x5 matrix, assign values, and print
  3. Intentionally create a memory leak in your code, then use valgrind (or Dr. Memory) to detect it and examine the output
100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏