When do stack allocations happen?

When a function is invoked, enough stack space for all its arguments and all its local
variables is allocated. [...] When the execution of the function terminates,
the stack space is deallocated by incrementing the stack pointer by the same value.

The block where a variable has been declared is called the scope of that variable.

You should think that any variable declaration creates an object, and that such an
object is destroyed when the scope where such variable has been declared ends.

These quotes are from the book "Beginning Rust" by Carlo Milanesi.

After reading them, my question is: is there a stack allocation and deallocation for each code block, or is it only done function-wide ?

Stack allocation for arguments is similar to that of local bindings.
So it happens right before a function is called (to set up the arguments for the function to use), and it can also happen at other times during execution e.g. in the middle of a block.

fn foo() {
    {
        let bar = 0;
    }
}

Is bar's space allocated before foo's content is executed or just before entering in the inner block ??

In what cases could it happen ?

What your quotes are saying is:

  • a slice of memory is allocated on the stack once per function
  • each local variable is created and destroyed somewhere in that slice of memory; the variable is created at the declaration and destroyed at the end of its block

However, I don't think the point about memory allocation happening function-wide is actually guaranteed: I can imagine that this can happen differently depending on optimizations or in a future version of Rust.

4 Likes

Crystal clear. Thanks.

Can you say more about why you're asking? What are you hoping to do with the answer?

There's a bunch of different possible answers at different levels of abstraction, and I don't know from your question which level you're interested in.

1 Like

I'm just trying to deepen my understanding of systems programming. some aspects of Rust's memory management are still fuzzy for me. So I read books and ask questions on the forum :smile:

For example, notably, inlining removes function calls, and with them steps of stack allocation and deallocation for those calls (instead, the stack of the calling function it got inlined into would be bigger accordingly).

Note that the stack space necessary for a local might be nothing, if it can live entirely in a register, or is completely removed by the optimizer. As with all things in a compiler, all documented behavior has an implicit "as if" in front, eg. "The program behaves as if storage for each variable is allocated on the stack"

In practice, there's an ABI for calling (external) functions that the compiler is constrained by that means it has to maintain a stack pointer/return address chain, and it's cheaper to move the pointer once than multiple times, so that pointer will (likely) only move once (if it had to: it doesn't for inlined functions and "leaf" functions that don't call other functions for example) at the start of the call, and variables are found relative to that. But if you have a series of variables that don't have overlapping lifetimes, the compiler can overlap, so "allocation" becomes a bit fuzzier a concept, depending on where the current instruction is, rather than running code!

2 Likes

So, the problem is that, as I said, the answer is different at different levels of that "deeper" understanding.

At the rust abstract machine level, yes, it's allocated when you "get" to the let. At the assembly level, the stack pointer is adjusted once at the entry to the function, then again before leaving the function, but not per let.

Thus in a way, two opposite answers are both correct. So it would help to add "and thus I should _______" to questions.

For example, if the question is "should I put everything into one let to avoid extra stack allocations?", then the answer is "no, definitely not, because the cost of needing one stack local is the same as needing multiple". But if the question is instead about "and thus I can use the storage of a value before its scope", then things are a whole different kind of complicated.

4 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.