{ }codelper

Learn › C

Language-specific · 35

Dynamic memory

Borrow exactly as much memory as you need at run time, and always give it back.

This lesson is for C++ · C.
Python, JavaScript, TypeScript, Java, C# and Craykon have a garbage collector that cleans up objects nobody uses any more, so you never write code to borrow and return memory.

When you only learn the size after reading the input, you borrow memory from the heap while the program runs.
The example below reads n, borrows n slots, fills them, prints them in reverse with their sum, and gives the memory back.

#include <stdio.h>#include <stdlib.h> int main(void) {    int n;    if (scanf("%d", &n) != 1 || n <= 0) return 0;     int *a = malloc(n * sizeof *a);  /* 힙에서 n칸을 빌려요 */    if (a == NULL) return 1;         /* 못 빌리면 NULL */     long long sum = 0;    for (int i = 0; i < n; i++) {    /* i <= n 이면 넘쳐요 */        scanf("%d", &a[i]);        sum += a[i];    }    for (int i = n - 1; i >= 0; i--)        printf("%d%c", a[i], i > 0 ? ' ' : '\n');    printf("%lld\n", sum);     free(a);   /* 빌린 만큼 한 번 돌려줘요 */    a = NULL;  /* 해제한 뒤에 실수로 쓰지 않게 */    return 0;}
stdin
5
3 1 4 1 5
stdout
5 1 4 1 3
14

Key points

  • So far arrays had a fixed size, like int arr[100].
    The heap lets you borrow exactly the size you decide at run time.
  • Locals (on the stack) vanish by themselves when the function ends.
    Heap memory stays until you give it back.
  • That means a function can return a result that outlives it.
    In exchange, giving the memory back is your job.
  • C++ ties memory to an object's lifetime (RAII).
    When the variable goes out of scope, its destructor gives the memory back, even if an exception is thrown or you return early.
  • Compile with gcc -fsanitize=address and leaks, use after free and overflows are caught at run time.

How each language writes it

C++Usually std::vector<int> a(n) (n slots filled with 0).
For a single object or a fixed-size array, a std::unique_ptr from std::make_unique<int[]>(n) — new int[n] / delete[] a exist too (delete for a single new; mismatching them is undefined behaviour), but modern C++ rarely writes them directly.
int *a = malloc(...) is a compile error in C++, because void * doesn't convert implicitly
Cmalloc(n * sizeof *a) from <stdlib.h> — returns NULL when memory runs out, and the slots hold garbage.
calloc(n, sizeof *a) fills them with 0; realloc(a, m * sizeof *a) may move the block, so use the new pointer it returns.
Give it back with free(a)

Common mistakes

Forgetting free

for (int k = 0; k < 1000; k++) {
    int *a = malloc(n * sizeof *a);
    /* … no free(a) */
}

→ The program runs fine, but its memory use keeps growing (a memory leak)

One free for every borrow.
In C++, leave it to std::vector.

Using after free, or freeing twice

free(a);
printf("%d\n", a[0]);
free(a);

→ Use after free is undefined behaviour, and the old value often still shows, which makes it harder to find. A double free usually makes glibc print free(): double free detected in tcache 2 and stop

Write a = NULL; after free(a);.
A mistaken use then usually crashes right away, and free(NULL) does nothing.

i <= n ran past the block

int *a = malloc(n * sizeof *a);
for (int i = 0; i <= n; i++)
    a[i] = 0;

→ It writes a[n], just past the n slots, and corrupts someone else's memory (a buffer overflow). Nothing may happen there, and the crash comes much later somewhere unrelated

Write i < n, and check with -fsanitize=address.

How languages differ
Give back what you borrow exactly once: one free for each malloc, calloc or realloc, delete for new, and delete[] for new[].
In C++ the safest choice is to leave this bookkeeping to std::vector and std::unique_ptr.

Practice