6

对不起,如果以前有人问过这个问题,但我没有找到任何东西......

对于“普通”x86 架构:

当我在 C++ 中调用一个大函数时,是否会立即为所有堆栈变量分配内存?或者是否有编译器可以(并且确实)修改堆栈大小,即使函数没有完成。

例如,如果一个新范围开始:

int largeFunction(){
    int a = 1;
    int b = 2;

    // .... long code ....

    { // new scope

         int c = 5;

         // .... code again ....

    }

    // .....

}

调用堆栈是否也可以在单独作用域的开头为变量 c “增长”并在其末尾“收缩”?还是当前的编译器总是会生成影响函数入口和返回值处的堆栈指针的代码?提前感谢您的回答。

4

3 回答 3

8

1)函数的运行时间与内存分配无关,与堆栈或堆无关。

2)何时“分配”堆栈仅取决于编译器生成最有效代码的方式。“高效”有着广泛的要求。所有编译器都可以选择修改优化器的速度和大小目标,并且大多数编译器还可以优化以降低堆栈消耗和其他参数。

3)自动变量可以进入堆栈,但这不是必须的。很多变量应该被“分配”到你的 cpu 的寄存器中。这大大加快了代码速度并节省了堆栈。但这在很大程度上取决于cpu平台。

4)编译器何时生成新的栈帧也是代码优化的问题。如果这样可以节省资源或更适合架构,编译器可以执行“乱序执行”。因此,无法回答何时使用堆栈帧的问题。新的作用域(左大括号)可以作为分配新堆栈帧的点,但这绝不是保证。有时,从实际作用域重新计算所有调用函数的所有堆栈相对地址并不有效。

5) 一些编译器还可以将堆内存用于自动变量。如果通过特殊指令进行访问作为堆栈相对寻址更快,则这种情况经常出现在嵌入式内核上。

但是通常编译器做他想做的事情并不是很重要。有时要记住的唯一一件事是,你必须保证你的筹码足够大。通常对新线程的系统调用有参数来设置堆栈大小。所以你必须知道你的实现需要多少堆栈大小。但在所有其他情况下:忘记思考。这项工作由您的编译器开发人员完美完成。

于 2013-10-10T17:24:44.740 回答
3

我不知道答案(我希望您只是想知道,因为您很好奇,因为没有有效的程序应该能够区分),但是您可以通过调用这样的函数来测试编译器的行为在新范围之前和新范围之后:

std::intptr_t stackaddr()
{
    int i;
    return reinterpret_cast<std::intptr_t>(&i);
}

如果您得到相同的结果,则意味着堆栈已经在创建之前进行了调整c

G++ 4.7 中有一个变化,它允许编译器在其作用域结束后重用堆栈空间c,以前在该点之后的任何新变量都会增加堆栈使用量:“G++ 现在正确重用分配给生命周期结束时的临时对象,这可以显着降低某些 C++ 函数的堆栈消耗。” 但我认为这只会影响函数入口时保留多少堆栈,而不是何时/何地保留。

于 2013-10-10T17:30:46.290 回答
0

这完全取决于您正在使用的系统的运行时约定,但是,CPU 架构通常在决定中发挥重要作用,因为架构定义了可以安全使用的堆栈管理。例如,在 MacOS X 下的旧 PowerPC 上,堆栈帧始终具有固定大小,新堆栈帧低端的堆栈指针的一个原子存储将分配它,取消引用堆栈指针相当于弹出整个堆栈帧。

当前的系统,如 Linux 和(纠正我,如果我错了)x86 上的 Windows 有一个更动态的方法,使用原子推送和弹出指令(PowerPC 上没有原子弹出),其中函数调用的参数被推送到在每个函数调用之前堆栈,每次都有效地调整分配的堆栈帧的大小。

所以,是的,在许多当前系统上,编译器可以调整堆栈帧的大小,但在其他系统上,这样的操作至少很难完成(尽管永远不可能)。

于 2013-10-10T17:34:43.003 回答