What Linux does when you malloc() ?

· Medium ·

4 min read Original article ↗

AnkitaDhiman

Press enter or click to view image in full size

malloc() is a C library function that asks the memory allocator for a block of memory. If it succeeds, you get a pointer to that memory. If it fails, you get NULL.

Press enter or click to view image in full size

A normal array has a fixed size. Sometimes, you don’t know how much memory you’ll need until the program is running.

char *p = malloc(1u << 30);  // 1 GB

if (p == NULL) {
/* allocation failed */
}

That’s where malloc() becomes useful. That is dynamic memory allocation.
In C++, std::vector gives you the same basic idea in a safer, more convenient way:

std::vector<int> arr(n);

The difference is that with malloc(), you manage the memory yourself — including calling free().
With std::vector, the container manages the memory and its lifetime for you. So the idea is the same.
fixed array → size known upfront
dynamic allocation /
vector → size can be determined at runtime

So Where Does malloc() Get Memory?

A useful starting point is to think about stack vs dynamically allocated memory.

int arr[100];          // typically stack
char *p = malloc(1000); // dynamically allocated

The stack is typically used for local variables. malloc() manages dynamically allocated memory. Depending on the allocation and allocator, that memory may come from the process heap (brk) or from mappings such as mmap().

And this is where things get interesting: getting memory from malloc() doesn’t necessarily mean physical RAM has been used yet.

Did malloc(1 GB) just use 1 GB of RAM ?

When you do:

p = malloc(1u << 30);  // 1 GB

your process gets a range of virtual addresses it can use.

Linux doesn’t necessarily need to have 1 GB of physical memory sitting in RAM immediately. Think of it like reserving addresses on a map.
You have the addresses, but the physical memory doesn’t necessarily have to be there yet.
So what happens when you actually use the memory?

p[0] = 42;

Now you’re accessing that memory.

If the page isn’t backed by physical memory yet, this access can trigger a page fault.

Linux can then provide physical backing for the page containing that address.

This gives us the important connection:

malloc() → virtual memory → memory access → page fault → physical memory
Can we actually see this? -> Yes.
Linux tools such as top can show us how much memory a process has.
The two numbers to watch are:

  • VIRT → virtual memory associated with the process
  • RES → memory currently resident in physical RAM

After:

p = malloc(1 GB);

you may see:
VIRT → large
RES → relatively small
Now touch the memory:

p[0] = 42;

The page containing that byte may need physical backing.
Then touch the memory page by page:

for (size_t i = 0; i < (1ULL << 30); i += 4096) {
p[i] = 42;
}

Now many pages are being accessed, so RES can grow significantly.

That’s the part that makes malloc() click:

Allocating memory and actually using physical memory are not necessarily the same event.

Try It Yourself

Compile with -O0 so the compiler doesn’t optimize away the memory accesses.
gcc -O0 malloc_test.c -o malloc_test
Then run it and watch the process in top.

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

int main(void) {
size_t n = (size_t)1 << 30;
char *p = malloc(n);
if (p == NULL)
return 1;

printf("after malloc pid=%d\n", getpid());
getchar();

p[0] = 42;
printf("touched first byte\n");
getchar();

for (size_t i = 0; i < n; i += 4096)
p[i] = 42;
printf("touched all pages\n");
getchar();

free(p);
return 0;
}

Then what happens when we free() it?

Press enter or click to view image in full size

free() — why RES may not drop by 1 GB

You allocated 1 GB. You free’d it. RES did not necessarily fall by 1 GB.

free() returns the block to the allocator. The allocator may keep it for the next malloc instead of giving it straight back to the kernel.

Large allocations are often handled with mmap() and may be unmapped when freed. Other allocations may remain in memory managed by the allocator.

If you say “leak,” say which number moved: VIRT, RES, or both.

One last surprise: malloc() succeeded. Am I safe?

Not necessarily.

Press enter or click to view image in full size

Linux can use memory overcommit, which means an allocation can succeed even though the system hasn’t provided all the physical backing immediately.

The real test may come later — when your program actually touches the memory.

If the system is under severe memory pressure, the OOM killer may eventually terminate a process.

So the mental model I started with was:

malloc() → “Give me RAM.”

The better mental model is:

malloc() → allocator → virtual memory → memory access → page fault → physical memory

A non-NULL pointer means:

“You have an address range you can use.”

It does not necessarily mean:

“1 GB of physical RAM is already sitting there for you.”

And that’s the part about malloc() I didn't understand before.