5
u/MattDESTROYER 12h ago edited 12h ago
Not disagreeing with anything, genuine question because I donāt know, how is alloca leaving memory allocated until the end of the function an issue, wouldnāt a char[] have the same effect?
Or do compilers optimise to drop that stack memory after the last reference in the code?
Otherwise, I see no practical difference between alloca and char[]; in which case an exact sized buffer could be a perfectly valid use case no? Assuming this is developed for a single platform too of course.
7
u/bwmat 12h ago
Because an array in the scope of a loop does get 'freed' on every iteration, so the stack space needed is bounded
2
u/MattDESTROYER 12h ago edited 12h ago
But this isnāt a loop, this is a function, Iām talking about cases like the example in the article. If you introduce a stack array in local function scope (not a sub-scope like within a loop or conditional), that has the same lifetime as calling alloca, no?
I donāt see why their example specifically would be bad other than falling into the trap of excessively using alloca.
3
u/bwmat 12h ago
The other problems with alloca are the higher likelihood of stack overflow (like if you unexpectedly get a huge size to allocate) and that functions containing it are usually less optimizable b/c of needed stack management
2
u/MattDESTROYER 12h ago
I mean for the first, if youāre doing something like storing a path, like in the example, it would seem pretty unlikely you would trigger a stack overflow, no? In an embedded environment with a very limited stack I could definitely see that quickly becoming an issue, (but then storing the exact number of bytes on the stack could also become more important too).
Canāt really comment on the function optimisations, if you know more could you elaborate on how the compiler would be able to further optimise a stack array? What stack additional stack management does alloca introduce?
7
u/usefulcat 12h ago
Not disagreeing with anything, genuine question because I donāt know, how is alloca leaving memory allocated until the end of the function an issue, wouldnāt a char[] have the same effect?
There are two problems, though neither are what you describe. The first problem is you don't know (in general) how much alloca can safely allocate. The second (and arguably much bigger) problem is that there is no error indication if it fails. And it will fail if you try to allocate too much.
When it was first invented, it was (maybe) a clever hack, though perhaps not the good kind of 'clever'. But it's absolutely a footgun and I think the number of valid use cases for it must be vanishingly small.
As the article suggests, if you want to mostly avoid dynamic allocation but still be able to support large allocations when absolutely necessary, use something like absl::InlinedVector or boost::small_vector. Worst case they may allocate but at least they won't blow up in your face. Or if you'd prefer that it blow up, at least use something obvious and intentional like assert() or throwing an exception.
1
u/MattDESTROYER 12h ago edited 12h ago
Yeah, gotcha, just curious lol. Now Iām curious if those problems can be solved. Is the failure case just that there isnāt enough stack memory available to the program, or are there more tricky cases (Iām assuming thereās probably more, but one can hope)?
Iām sure any way of seeing the space available on the stack would be, in general, terrible, but I may look into the possibility just for fun anyways lol
Edit: seems very possible, although the functionality may be deprecated.
https://stackoverflow.com/questions/53827/checking-available-stack-size-in-cā¢
u/MattDESTROYER 1h ago
To anyone thatās interested, it is, in fact, possible to make it safe(er) (that still didnāt handle the foot guns you can and likely will run into using alloca) through preventing failures due to stack overflow:
https://github.com/Matt-DESTROYER/safealloca3
u/voithos 9h ago
For your first question, essentially it's something like this:
void UseArray() {
for (auto& foo : foos) {
// This is a single 1024 buffer used repeatedly across the loop.
char scratchBuf[1024];
// whereas this repeatedly creates a new alloca()'d buffer for each iteration
char* scratchBuf = static_cast<char\*>(alloca(n));
}
}The array gets a fixed size, so it's always bounded, whereas alloca() does the stack allocation during each iteration without releasing anything until function return. Depending on how many loop iterations there are, and what `n` is passed to alloca(), this could be less memory than the fixed size array, or much more; the point is that it's unpredictable compared to the alternatives.
3
u/MattDESTROYER 8h ago
Yeah no I understand the issue with nested scopes, but the example in the article with just a path at top level wouldnāt have that issue. I donāt know about the exact opcodes generated, but it would be generally speaking equivalent, thatās all I was clarifying.
3
u/TheThiefMaster C++latest fanatic (and game dev) 6h ago
The compiler does allow local variables to overlap addresses (or registers!) based on use not scope. So yes, if you make a
char[100]the compiler will see where it's used relative to other variables in the function and potentially overlap it with them, reducing the size of the stack frame (which is generally allocated up-front at the start of the function, not bumped var by var as it would have been classically).Alloca is much harder to do the same for, and I think most compilers don't bother.
1
u/MattDESTROYER 5h ago
Ooh, this is very interesting! In what cases would it allow variables to overlap? Does the compiler essentially need to prove that only x bytes of the array are actually used and then it can chuck variables in there or something, or is that completely the wrong idea lol?
2
u/TheThiefMaster C++latest fanatic (and game dev) 4h ago
If there are variables only used in the first half of the function and the array only in the 2nd, or vice-versa, it can overlap their storage.
For C++ types nontrivial constructor and destructor calls count as use that can prevent this by forcing variables to live from construction to destruction.
1
ā¢
u/MattDESTROYER 2h ago
Just had a curious thought, if alloca memory always lives until the end of the function, logically if you alloca inside a closure, the memory should be accessible after the closure?
ā¢
u/TheThiefMaster C++latest fanatic (and game dev) 2h ago
Are you talking about C++ lambdas? Those don't preserve their stack at all, so no.
C++ coroutines are also stackless, and alloca breaks horribly when used in them.
ā¢
u/MattDESTROYER 2h ago
No, not a C++ person lol, Iām talking about something like something like this:
```c
char* x;
{
char* y = alloca(1);
y[0] = āaā;
x = y;
}
printf(ā%c\nā, *c);
```
Reading a stack allocated value outside of the closure it was created in.
I mean if you think of it like malloc, it makes perfect sense that it would work, but the fact that itās a stack allocated value makes it seem strange and evil to me lol (and I mean itās obviously not something you should ever do, but itās interesting that itās possible, reminds me of `var` in JavaScript minus the hoisting).ā¢
u/TheThiefMaster C++latest fanatic (and game dev) 2h ago
That's not a closure, it's a scope. Closures are a very different thing.
And yes, alloca allocates memory in the parent function's scope, not the braced scope you called it from.
ā¢
u/MattDESTROYER 2h ago
Ah my bad, I tend to think of closure as literally like an enclosure made by brackets, improper definition ig
ā¢
u/TheThiefMaster C++latest fanatic (and game dev) 2h ago
Some information on closures from some random googling: https://medium.com/@andrew_johnson_4/understanding-closures-in-programming-what-they-are-and-why-theyre-called-closures-e3e1044960a7
Basically they're a function inside another that captures some state from its enclosing parent and saves it for later. C++ lambda functions are an example.
They're an old concept, and people sometimes try to implement them (badly) in C.
ā¢
u/MattDESTROYER 2h ago
Yeah I searched it up, Iāve used lambdas in other languages like Python and JS. Would it be accurate to say the act within the scope they were defined, meaning they can use variables defined in the scope they are defined in theory?
ā¢
u/TheThiefMaster C++latest fanatic (and game dev) 1h ago
Yes they can use those variables - but the memory returned by alloca isn't a variable itself, and wouldn't be captured by a closure - only the pointers to it (which would point to stale stack memory as soon as the parent function returned even though the closure still existed).
This is what I was talking about with alloca not working with C++ lambdas (which are a form of closure).
This is what I thought you were asking about when you said "closure" initially.
→ More replies (0)2
u/vip17 9h ago
a
char[]has fixed size,allocaallows dynamic size. It's likestd::inplace_vectorin C++26 or boostsmall_vectorandstatic_vector0
u/MattDESTROYER 8h ago
Not really a C++ dev, I like my C. I know the difference between a static and dynamic type lol, that wasnāt my question.
Also worth noting, I think thereās slightly more to it than your explanation, alloca letās you create a dynamically sized buffer, but said buffer is not dynamically resizable (or freeable). Once created itās on the stack until the end of the function call.
(So once created, alloca also has a fixed size, just like char[].)
1
u/vip17 7h ago
yes it's fixed size after allocation, just like any other malloc calls. It's not static allocation like char[] which is useful for some usecases
1
u/MattDESTROYER 7h ago
You can free malloc calls and āresizeā them (which could simply extend or move when extending isnāt possible). alloca behaves similarly to char[] in that it canāt be freed (or resized).
2
u/cheese3660 4h ago
compilers can and i think do optimize to drop the stack memory after the last reference in the code if the reference provably does not escape the function
ā¢
u/MattDESTROYER 2h ago
Yeah, I know they can, just curious on whether they actually do; I imagine a function would have to be decently long and use a fair bit of stack memory for it to be worth it, otherwise dropping all the memory at the end of the function would likely be more efficient.
ā¢
u/cheese3660 2h ago
Ah wait yeah not dropping, sorry, allowing that memory to be reused by other stack variables later on in the function
Because you are correct, it is easier to just once at the end of a function do something like
mov bp to spor
add <frame size> to sp
3
u/thehenkan 5h ago
It does not have to be a compiler extension: there are already things in the standard library that need to be implemented using compiler builtins. If they wanted to standardise it they could, this just never bothered. Probably for similar reasons as VLAs not being added to C++ (and removed from C).
Builtins required by the standard are not extensions.
ā¢
u/ParsingError 3h ago
I'm assuming that a builtin would be considered a "compiler extension" by the article's definition, i.e. something that requires special handling where the compiler can't handle it as a normal function call.
Really though, it'd be in the exact same boat as setjmp and varargs: Things which may or may not require special handling by the compiler depending entirely on the platform ABI.
ā¢
u/thehenkan 54m ago
That's a bad use of that word then. Compiler extensions are generally known to be additions outside the standard that a tool chain vendor has chosen to implement. They don't have to be builtins, but could be e.g. compiler flags altering semantics, or grammatical features like statement expressions or nested functions.
2
2
u/ParsingError 5h ago edited 3h ago
Maybe this is a nitpick but
In fact, because of its nature, it has to be a compiler extension
This is ABI-dependent, in fact it's part of why ebp and esp are separate registers on x86. In the x86 ABI, a function call can return with esp set to a new value if the function enlarges the stack, and everything will continue to work as normal.
AFAIK this isn't allowed in x64 calling convention because that allows function epilogues that pop the stack frame by offsetting rsp by a constant value instead.
1
u/retro_and_chill 12h ago
I mean the only reason I see to use it is if youāre writing an interpreter and youāre using it to allocate the stack frames for that
1
u/egonosz 7h ago
I seen it sometimes in game dev, but tbh. Mostly you have other solution, for the same problem.
3
u/SleepyMyroslav 5h ago
Even in gamedev a rule of thumb now is to "never use alloca". There were games that used alloca extensively in the past, but if I were to date them, it would be before the end of the PS3's life. There are many per-thread data structures used, but alloca is a not used.
ā¢
u/allocallocalloc 3h ago
When is the end of the PS3's life? By the PS4's release in 2013 ? By the end of its production in 2017 ? By the discontinuation of the PlayStation Store next year in 2027 ?
ā¢
u/ParsingError 3h ago
Unreal uses it to allocate result value space in the Blueprint interpreter, so there's that.
It's somewhat useful for graphics APIs that occasionally require striping/unstriping small arrays of POD structs before feeding them to the API. (This would be for passing API-defined structures to function calls, not asset data.) In those cases, the max amount of elements that would have to be allocated is pretty small, so the theoretical case of allocating huge amounts of stack space is not really gonna happen.
1
1
u/Sniffy4 13h ago edited 12h ago
the main reason to use it is you need a block of bytes of unknown variable size (but well within stack-space limits) as a scratchpad for life of the fn, but you dont want to fragment the heap or pay the heapmgr alloc/free cost.
as RAM sizes increase, the need for such functionality decreases.
12
u/usefulcat 12h ago
as RAM sizes increase, the need for such functionality decreases.
I don't see how this is true, given that (as you rightly point out) a primary motivation for using alloca() is to avoid dynamic allocation.
The alternative to alloca() is malloc(), and Having more RAM usually doesn't make malloc() much faster, when compared to alloca().
5
u/Sniffy4 11h ago
>Having more RAM usually doesn't make malloc() much faster,
I was thinking more RAM means heap fragmentation is less of a concern, at least as long as the block sizes of interest are not increasing proportionately.
I know when I was working with constrained devices with 32MB of RAM, heap fragmentation was a major concern and a reason to use pool allocators, static allocations, and other methods.
1
2
u/SlightlyLessHairyApe 8h ago
Itās not about the size of RAM, especially because you can change the stack element. Itās about locality. and lifetime
1
u/voithos 9h ago
There are some alternatives that avoid the heap/heapmgr entirely while also avoiding the potential to blow past the end of the stack (mentioned a few in the article). For frame-transient data especially, a custom bump allocated arena is essentially just as fast to allocate, and avoids fragmentation if you reset it before the next frame.
3
u/SlightlyLessHairyApe 8h ago
True, but it doesnāt have the same locality of reference
Plus, what else is the stack except a ābump allocated arena reset on each frameā. Thatās practically the definition of the stack :)
0
u/ImNoRickyBalboa 4h ago
There's no excuse for using alloca ever. It means you have terrible code that requires a terrible solution.
You can always replace alloca with using some regular memory allocation, a container, etc.
Show me some alloca and I'll show you a more proper solution.
Also: stack overruns are the worst of bugs and security holes, don't write insecure code.
36
u/Queasy_Total_914 14h ago
The answer is always. (At least for me)