scvalex.net

6. Malloc Never Fails

Here’s a fun bit of trivia: malloc on Linux never fails [due to the obvious cause]. In this post, we’ll show that this indeed is the case, and explore why this happens.

First off, what should happen? The malloc(3) manpage only says that, “On error, these functions return NULL.”, and it’s careful not to specify what the error conditions are.

Let’s try it out; here’s a program that repeatedly allocates \(1\) GB memory chunks until it fails.

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

int main(int argc, char *argv[]) {
    printf("Before\n");
    int i = 0;
    while (malloc(1 << 30)) {
        printf("Allocated %d GB\n", ++i);
    }
    printf("After\n");

    return 0;
}

Running it, I see it failing after allocating about \(130315\) GB, which, needless to say, is a lot more than the memory available on my laptop.

If we strace(1) the above program, we see that it’s mmap(2)ing 1 GB chunks of private, anonymous memory for file descriptor \(-1\). Checking the manpage, we see that this is indeed the portable way to allocate memory on Unix.

...
mmap(NULL, 1073745920, PROT_READ|PROT_WRITE,
     MAP_PRIVATE|MAP_ANONYMOUS, -1, 0)
= 0x7d6706f76000
write(1, "Allocated 2627 GB\n", 18) = 18
...

With this in mind, we can now see individual and total allocations for any program with commands like the following:

strace ls 2>&1 >/dev/null | grep mmap

strace ls 2>&1 >/dev/null | grep mmap | \
  awk '{ total += $2 } END { print total }'

# ls allocates around 14561777 over a run

Clearly, ls isn’t using 14 MB to run, so what’s going on? The answer is that Linux allocates memory pages for processes lazily. So, regardless of how much memory you ask for, it will just give it to you, but it will only really reserve it when you try to write to it. This is basically a combination of virtual memory, and demand paging.

But wait, you ask, if that’s the case, why does it fail eventually? Both the kernel and glibc have an overhead when allocating memory. In my case, I saw the test program chew through about \(3\) GB of memory after allocating \(130315\) GB. A quick calculation gives us the overhead per 1 MB chunk:

3 GB / 130315 / (1 GB / 1 MB) = 24 B

That’s about 24 bytes overhead for each 1 MB of memory, which seems reasonable, and it explains why we usually don’t even notice the overhead. Section III of this Phrack article is a down to earth description of how the glibc malloc implementation works, if you’re curious.

So, malloc on Linux only fails if there isn’t enough memory for its control structures. It does not fail if there isn’t enough memory to fulfill the request.


To clarify, the surprising behaviour malloc has does not mean we should ignore its return value. We just need to be careful because malloc returning successfully does not always mean that we can use the requested memory.


@mete notes in the comments that the situation is somewhat more complicated: the Linux kernel uses a heuristic to determine if giving a process more memory than it has will lead to something bad happening.


I was wrong about why malloc finally failed! @GodmarBack observes, in the comments, that x64 systems only have an address space of \(48\) bits, which comes out to about \(131000\) GB. So, on my machine at least, the malloc finally failed because of address space exhaustion.