问题 堆栈溢出:堆栈空间中的临时分配重复?


struct MemBlock {

    char mem[1024];

    MemBlock operator*(const MemBlock &b) const {

        return MemBlock();
    }

} global;

void foo(int step = 0) {

    if (step == 10000)
    {
        global = global * MemBlock();
    }
    else foo(step + 1);
}

int main() {

    foo();
    return 0;
}

程序接收信号SIGSEGV,分段故障。   foo中的0x08048510(步骤= 4000)at t.cpp:12   12 void foo(int step = 0){

看起来MemBlock()实例虽然还没有被调用但是要花费很多堆栈内存(请查看gdb信息)。

当我使用时 global = global * global 相反,程序正常退出。

任何人都可以解释内在的机制吗?


5001
2017-07-20 12:20


起源

您收到段错误是因为您递归调用foo 10000次,它与MemBlock类无关 - Tom Knapen
@TomKnapen,不,你错了。 a)它因4001的电话而死。 b)如果你去掉里面的东西 if,它根本不死。 - SingerOfTheFall
@SingerOfTheFall我的评论可能会更好,因为它试图“试图调用foo 10000次”,但这一点仍然存在:这是递归的错误。如果删除if块,编译器可能会优化函数调用,从而删除递归。 - Tom Knapen
您可能想要添加 int* test = &step 变量并观察调用堆栈上的不同值。我敢打赌这会显示调试器中分配的实际堆栈内存。 - MSalters


答案:


编译器正在为此保留堆栈空间 MemBlock 每次调用时的实例 foo,无论内部的控制流程如何 foo。这是一种常见的优化,可以防止在函数内重复调整堆栈指针。而是,编译器计算 最大 需要堆栈空间并在进入函数时调整堆栈指针的数量。

正如您所观察到的,这会导致丢失为您实际未使用的对象保留的堆栈空间。答案是不这样做;如果你只是在某些分支中使用一些大型足迹对象,那么将这些分支分离出来自己的功能。

顺便说一句,这就是为什么古老版本的C需要在函数顶部声明所有函数范围变量的原因;这样编译器就可以轻松计算出函数需要多少堆栈空间。


15
2017-07-20 12:29



我喜欢最后的历史课,我不知道那个花絮:) - John Humphreys - w00te
我试着用表达式替换表达式 MemBlock *t = new MemBlock; global = global * *t; 我认为它将在运行时构建,它不会影响堆栈空间。但还是有错。有什么问题? - Determinant
+1为 “如果你只是在某些分支中使用一些大型足迹对象,那么将这些分支分离出来放入自己的功能中。”.. 和  “顺便说一句,这就是为什么古老版本的C需要在函数顶部声明所有函数范围变量的原因;这样编译器可以很容易地计算出函数需要多少堆栈空间” - Nawaz
@ymfoi在这种情况下,编译器正在为此保留堆栈空间 返回值 的表达 global * *t! - ecatmur
@ecatmur对不起,我不太明白。什么是“回报价值”? - Determinant