使用卷曲支架从堆栈中删除分配

Use curly braces to remove allocations from the stack

本文关键字:堆栈 删除 分配      更新时间:2023-10-16

假设我在一种方法中有很多堆栈分配。

如果我将卷曲括号添加到一组代码中,则分配的对象会在范围内弹出堆栈,还是在发布内存之前需要退出该方法?

我还应该补充说,这是在MFC应用程序中完成的。

void LongMethod()
{
    {
        struct someLargeStruct;
        // Do a lot of work and allocations.
    }
    {
        struct anotherLargeStruct;
        // more work here.
    }   
}

只是为了进一步澄清 - 是的,标准需要在块范围末尾释放自动存储,请参见[basic.stc.auto]/1:

块范围变量明确声明为register或未明确声明为staticextern具有自动存储持续时间。这些实体的存储将持续到创建其创建的块出口为止。

但是,需要编译器仅实现程序的可观察行为( as-if 规则):

...需要实现(仅)(仅)抽象机的可观察行为...

由于该标准将自动存储视为无限的,并且没有其他方法可以观察堆栈使用情况,因此,在AS-IF规则下,符合编译器的编译器并不是在每个范围结束时都严格释放内存。p>的确,我们观察到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字节(xy共享相同的空间):

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字节(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

是的,是的,块scopes有一些效果,但不要指望它与这些范围完全一致。

它可能在递归功能中特别令人烦恼(示例):

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 中没有 stack (至少不是您想到的方式,有std::stack容器适配器,但完全不同)。

但是,有一个自动存储,通常在堆栈上实现,但不能(例如,它可以在寄存器中)。

但是,对于自动存储,

对象的存储在开始时分配 封闭代码块并在末尾进行划分。所有本地对象 有此存储持续时间,除了那些被宣布为静态,外部或 thread_local。

(https://en.cppreference.com/w/cpp/language/storage_duration)

这意味着,如果编译器决定使用堆栈存储变量,则应知道其存储在封闭块的末尾结束。而且,尽管编译器目前没有义务减少堆栈指针,但这将是实施问题的严重质量。