假设我在一个方法中有很多堆栈分配。
如果我将大括号添加到一组代码中,当分配的对象超出范围时是否会从堆栈中弹出,或者方法是否需要在释放内存之前退出?
我还要补充一点,这是在MFC应用程序中完成的。
void LongMethod()
{
{
struct someLargeStruct;
// Do a lot of work and allocations.
}
{
struct anotherLargeStruct;
// more work here.
}
}
只是为了进一步澄清这一点 - 是的,标准要求在块范围结束时释放自动存储,请参阅[basic.stc.auto]/1:
块范围变量显式声明为
register
或未明确声明为static
或extern
具有自动存储持续时间。这些实体的存储将持续到创建它们的块为止。
但是,编译器只需要实现程序的可观察行为(the as-if rule):
...实现需要模拟(仅)抽象机器的可观察行为......
由于标准将自动存储视为无限制,并且没有其他方法可以观察堆栈的使用,因此在as-if规则下,符合标准的编译器没有义务严格要求在每个范围的末尾准确释放内存。
实际上我们观察到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);
}
}
因此,将重堆栈用户隔离到单独的功能中会更安全。
C ++中没有堆栈(至少,不像你想象的那样,有std::stack
容器适配器,但它完全不同)。
但是,有一个自动存储,通常在堆栈上实现,但可能不是(例如,它可能在寄存器中)。
但是,对于自动存储,
对象的存储在封闭代码块的开头分配,并在最后解除分配。除了声明为static,extern或thread_local的那些外,所有本地对象都有此存储持续时间。
(Qazxswpoi)
这意味着如果编译器决定使用堆栈来存储变量,它应该知道它们的存储在封闭块的末尾结束。虽然编译器在这一点上没有义务减少堆栈指针,但是这将是一个严重的实现质量问题。