假设我在一种方法中有很多堆栈分配。
如果我将花括号添加到一组代码中,则分配的对象超出范围时会从堆栈中弹出,还是在释放内存之前需要退出该方法?
我还应该补充一点,这是在MFC应用程序中完成的。
void LongMethod()
{
{
struct someLargeStruct;
// Do a lot of work and allocations.
}
{
struct anotherLargeStruct;
// more work here.
}
}
答案 0 :(得分:3)
C ++中没有 stack (至少,按照您的想法,它没有std::stack
容器适配器,但完全不同)。
但是,有一个自动存储,该存储通常在堆栈上实现,但可能不是(例如,可以在寄存器中)。
但是,对于自动存储,
对象的存储空间分配在 封闭代码块并在末尾释放。所有本地对象 具有此存储期限,但声明为static,extern或 thread_local。
(https://en.cppreference.com/w/cpp/language/storage_duration)
这意味着,如果编译器决定使用堆栈存储变量,则应知道它们的存储在封闭块的末尾结束。尽管此时编译器没有义务减少堆栈指针,但如果不这样做,将是严重的实现质量问题。
答案 1 :(得分:3)
只是为了进一步澄清这一点-是的,该标准要求在块作用域的末尾释放自动存储,请参见[basic.stc.auto]/1:
显式声明为
register
或未显式声明为static
或extern
的块范围变量具有自动存储持续时间。这些实体的存储将持续到创建它们的块退出为止。
但是,要求编译器仅实现程序(the as-if rule)的可观察的行为:
...需要实现来模拟(仅)抽象机的可观察行为...
由于该标准将自动存储视为无限存储,并且没有其他方法可以观察堆栈的使用情况,因此,在符合规则的情况下,符合要求的编译器不必严格要求在每个作用域的末尾完全释放内存。
实际上,我们观察到GCC,clang和MSVC倾向于在函数启动时分配一次堆栈空间,而在函数退出时进行分配。尽管至少它们似乎在不同的块作用域之间重用了内存:
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);
}
}
分配的堆栈帧:800 bytes(x
和y
共享相同的空间):
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
在没有范围的情况下,行为会稍有变化,我们发现不再有内存重用:
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);
}
分配的堆栈帧: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
因此,可以得出结论,是的,区块范围有一定的作用,但不要期望它与这些范围完全一致。
在递归函数(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);
}
}
因此将大量堆栈用户隔离到单独的功能中会更加安全。