Just to clarify this a little further - yes the standard requires automatic storage to be freed at the end of the block scope, see [basic.stc.auto]/1:
Block-scope variables explicitly declared register
or not explicitly declared static
or extern
have automatic storage duration. The storage for these entities lasts until the block in which they are created exits.
However, the compiler is required to implement only the observable behavior of the program (the as-if rule):
...implementations are required to emulate (only) the observable behavior of the abstract machine...
Since the standard treats automatic storage as unlimited and there is no other way to observe stack usage, under the as-if rule a conforming compiler is not obliged to be strict about freeing memory exactly at the end of each scope.
Indeed we observe that GCC, clang and MSVC prefer to allocate stack space once upon function start and deallocate at function exit. Although at least they seem to re-use memory between different block scopes:
int do_something_with(int*);
int do_something_with(long long*);
void do_something()
{
{
int x[100];
do_something_with(x);
}
{
long long y[100];
do_something_with(y);
}
}
Stack frame allocated: 800 bytes (x
and y
share the same space):
do_something():
sub rsp, 808
mov rdi, rsp
call do_something_with(int*)
mov rdi, rsp
call do_something_with(long long*)
add rsp, 808
ret
Without the block-scopes the behavior changes slightly, we observe no memory re-use anymore:
int do_something_with(int*);
int do_something_with(long long*);
void do_something()
{
int x[100];
do_something_with(x);
long long y[100];
do_something_with(y);
}
Stack frame allocated: 1200 bytes (x
+ y
):
do_something():
sub rsp, 1208
mov rdi, rsp
call do_something_with(int*)
lea rdi, [rsp+400]
call do_something_with(long long*)
add rsp, 1208
ret
So to conclude, yes, there is some effect of the block-scopes, but don't expect it to be exactly aligned to those scopes.
It can be especially vexing in recursive functions (example):
int do_something_with(long long*);
int bar();
void foo()
{
{
if (bar())
foo(); // don't count on this being efficient
}
{
long long y[10000];
do_something_with(y);
}
}
It's much safer therefore to isolate heavy stack users into separate functions.