4

Why does alloca not check if it can allocate memory?

From man 3 alloca:

If the allocation causes stack overflow, program behavior is undefined. … There is no error indication if the stack frame cannot be extended.

Why alloca does not / can not check if it can allocate more memory?

The way I understand it alloca allocates memory on stack while (s)brk allocates memory on the heap. From https://en.wikipedia.org/wiki/Data_segment#Heap :

The heap area is managed by malloc, calloc, realloc, and free, which may use the brk and sbrk system calls to adjust its size

From man 3 alloca:

The alloca() function allocates size bytes of space in the stack frame of the caller.

And the stack and heap are growing in the converging directions, as shown in this Wikipedia graph:

enter image description here

(The above image is from Wikimedia Commons by Dougct released under CC BY-SA 3.0)

Now both alloca and (s)brk return a pointer to the beginning of the newly allocated memory, which implies they must both know where does the stack / heap end at the current moment. Indeed, from man 2 sbrk:

Calling sbrk() with an increment of 0 can be used to find the current location of the program break.

So, they way I understand it, checking if alloca can allocate the required memory essentially boils down to checking if there is enough space between the current end of the stack and the current end of the heap. If allocating the required memory on the stack would make the stack reach the heap, then allocation fails; otherwise, it succeeds.

So, why can't such a code be used to check if alloca can allocate memory?

void *safe_alloca(size_t size)
{
    if(alloca(0) - sbrk(0) < size) {
        errno = ENOMEM;
        return (void *)-1;
    } else {
        return alloca(size);
    }
}

This is even more confusing for me since apparently (s)brk can do such checks. From man 2 sbrk:

brk() sets the end of the data segment to the value specified by addr, when that value is reasonable, the system has enough memory, and the process does not exceed its maximum data size (see setrlimit(2)).

So if (s)brk can do such checks, then why can't alloca?

  • I think `alloca` is basically mimicking what's done when local variables are allocated on the stack in a called function. Note that C99 adds support for variable-sized arrays, making `alloca` less important. – Tom Karzes Oct 14 '17 at 10:55
  • Also, note that just because there's room in the address space, it does not necessarily mean `alloca` will succeed. In most cases, your stack size limit will determine whether the stack can be grown. This limit will normally be reached before you run out of address space. – Tom Karzes Oct 14 '17 at 10:57
  • Could you explain how that diagram works when there are 10 threads? (each thread has its own stack, but there is a common heap) – M.M Oct 14 '17 at 11:31
  • BTW your image is too simplistic these days.... Try `cat /proc/$$/maps` to understand – Basile Starynkevitch Oct 14 '17 at 11:33
  • @M.M OK, I get it, the diagram is outdated. Now I read in the Wikipedia: "*The stack area traditionally adjoined the heap area and they grew towards each other; when the stack pointer met the heap pointer, free memory was exhausted. With large address spaces and virtual memory techniques they tend to be placed more freely*" https://en.wikipedia.org/wiki/Data_segment#Stack –  Oct 14 '17 at 11:34
  • Why the downvote? –  Oct 14 '17 at 11:35
  • @gaazkam Don't worry about the downvote. It wasn't me (I think it's an interesting enough question), but I suspect it's because someone thought it was a "bad question". As applied here, that logic seems to be: "`alloca` is a bad idea, so you shouldn't be using it, so you shouldn't be asking questions about it, either." There's an element on Stackoverflow that doesn't like theoretical questions, and seems to believe you're only supposed to ask about the proper use of well-defined behavior. (I don't agree with this sentiment at all, but I see it all the time.) – Steve Summit Oct 14 '17 at 18:35

2 Answers2

4

alloca is a non-standard compiler intrinsic whose selling point is that it compiles to extremely lightweight code, possibly even a single instruction. It basically does the operation performed at the beginning of every function with local variables - move the stack pointer register by the specified amount and return the new value. Unlike sbrk, alloca is entirely in userspace and has no way of knowing how much stack is left available.

The image of stack growing towards the heap is a useful mental model for learning the basics of memory management, but it is not really accurate on modern systems:

  • As cmaster explained in his answer, the stack size will be primarily limited by the limit enforced by the kernel, not by the stack literally colliding into the heap, especially on a 64-bit system.
  • In a multi-threaded processes, there is not one stack, but one for each thread, and they clearly cannot all grow towards the heap.
  • The heap itself is not contiguous. Modern malloc implementations use multiple arenas to improve concurrent performance, and offload large allocations to anonymous mmap, ensuring that free returns them to the OS. The latter are also outside the single-arena "heap" as traditionally depicted.

It is possible to imagine a version of alloca that queries this information from the OS and returns a proper error condition, but then its performance edge would be lost, quite possibly even compared to malloc (which only occasionally needs to go to the OS to grab more memory for the process, and usually works in user-space).

user4815162342
  • 141,790
  • 18
  • 296
  • 355
  • 1
    Also, even if an implementor wanted to write "a version that queries this information from the OS", there would be the issue that the OS probably doesn't even provide a way to query it. So another answer to the OP's question is that "fixing" `alloca` is a two-step process, and no one has felt the need to implement both steps yet. – Steve Summit Oct 14 '17 at 12:33
  • Why would an enhanced-`alloca` need to query this information from the OS? Surely it could be made available and stored somewhere in the program at the time a stack is allocated? – Alex Celeste Oct 14 '17 at 12:41
  • @SteveSummit Agreed, the hypothetical fixed `alloca` would require a concerted effort by the compiler and the OS vendors. This kind of thing is not unheard of, but it needs a serious payoff to be undertaken. – user4815162342 Oct 14 '17 at 12:42
  • @SteveSummit Why would this be that difficult, wouldn't `getrlimit(RLIMIT_STACK, &response)` be enough? `man 2 getrlimit` –  Oct 14 '17 at 12:48
  • @user4815162342 As above? –  Oct 14 '17 at 12:48
  • 1
    @Leushenko @gaazkam Do we know that the stack limit can't change, e.g. by a call to `setrlimit`? – user4815162342 Oct 14 '17 at 14:10
  • @Leushenko Surely it could be -- but the point is that it isn't generally. (Although, in response to other comments, I had forgotten about `getrlimit`, which might help -- or it might not.) So while these arguments may not seem compelling, ultimately, I took gaazkam's question to be a practical, not a theoretical one. We're explaining the practical reasons why `alloca` hasn't been made to check stack availability, not any theoretical reasons why it would be impossible for it to do so. – Steve Summit Oct 14 '17 at 18:31
3

The picture is a bit outdated: On a modern system, the heap memory regions and the memory region that contains the call stack are entirely separate entities, and they are very far apart on a 64 bit system. The concept of the memory break was designed for systems with small address spaces.

As such, the limitation for the stack space is not that it may grow into the top of the heap, the limitation is that the kernel may not have any memory left to back it. Or the kernel may decide that your stack has grown too much (some limit is reached), and thus shoots down your process.

Your process grows the stack simply by decrementing the stack pointer, and storing some data there. If that memory access is currently not mapped into your address space, the hardware immediately signals this condition to the OS kernel, which checks where the failed memory access occurred, and if that is just below the stack memory region, it will immediately expand that memory mapping, map a new page of memory there, and give control back to your process to retry its memory access. The process does not see any of this. It just sees that its access to stack memory succeeded.

alloca() does not deviate from this in any way: You ask it to allocate some memory on the stack, and it does so by decrementing the stack pointer accordingly. However, if your allocation is so large that the OS does not see memory accesses to it as valid stack memory accesses, it will (likely and hopefully) kill your process with a SEGFAULT. However, since the behavior is undefined, anything may happen.

klutt
  • 30,332
  • 17
  • 55
  • 95
cmaster - reinstate monica
  • 38,891
  • 9
  • 62
  • 106
  • 1
    Comment on last sentence: Well, stack overflow is undefined behavior. You may get segfault, if you're lucky. But something else may also happen (such as malware program, which caused the overflow by exploiting a security vulnerability, emptying your bank account and starting global thermonuclear war). – hyde Oct 14 '17 at 11:20
  • @hyde Right. I just accepted klutt's edit that made the exact change :-) – cmaster - reinstate monica Oct 14 '17 at 15:12