如果之前有人询问我很抱歉,但我没找到任何东西......
对于“普通”x86架构:
当我在C ++中调用一个大函数时,是否会立即为所有堆栈变量分配内存? 或者是否有编译器可以(并且确实)修改堆栈大小,即使函数没有完成。
例如,如果新范围开始:
int largeFunction(){
int a = 1;
int b = 2;
// .... long code ....
{ // new scope
int c = 5;
// .... code again ....
}
// .....
}
调用堆栈是否也可以在单独范围的开头“变长”变量c并在其末尾“收缩”? 或者当前编译器是否总是生成影响函数入口和返回值的堆栈指针的代码? 谢谢你的回答。
答案 0 :(得分:8)
1)函数与内存分配无关,与堆栈或堆无关。
2)当堆栈被“分配”时,仅取决于编译器制作最有效代码的方式。 “高效”具有广泛的要求。所有编译器都可以选择修改优化器目标以获得速度和速度。大小和大多数编译器也可以优化低堆栈消耗和其他参数。
3)自动变量可以进入堆栈,但这不是必须的。应该将许多变量“分配”到cpu的寄存器中。这样可以大大加快代码速度并节省堆栈。但这在很大程度上取决于cpu平台。
4)当编译器生成新的堆栈帧时,也是优化代码的问题。如果这可以节省资源或更好地适应架构,编译器可以执行“乱序执行”。因此无法回答堆栈帧使用时的问题。新范围(开括号)可以是分配新堆栈帧的点,但这绝不是保证。有时,从实际范围重新计算所有被调用函数的堆栈相对地址是没有效率的。
5)有些编译器也可以将堆内存用于自动变量。如果通过特殊指令访问作为堆栈相对寻址更快,则通常可以在嵌入式内核上看到这种情况。
但是,当编译器按照自己的意愿行事时,通常不是很重要。有时要记住的唯一事情是,你必须保证你的堆栈足够大。系统调用新线程通常都有参数来设置堆栈大小。因此,您必须知道实现所需的堆栈大小。但在所有其他情况下:忘记考虑。这项工作完全由编译器开发人员完成。
答案 1 :(得分:3)
我不知道答案(我希望你只想知道,因为你很好奇,因为没有一个有效的程序应该能分辨出来),但是你可以通过调用一个来测试编译器的behaiour在新范围之前和新范围之后再次使用此函数:
std::intptr_t stackaddr()
{
int i;
return reinterpret_cast<std::intptr_t>(&i);
}
如果得到相同的结果,则意味着在创建c
之前已经调整了堆栈。
G ++ 4.7发生了变化,允许编译器在其范围结束后重新使用c
的堆栈空间,之前在该点之后的任何新变量都会增加堆栈的使用:{{3但我认为这只会影响在进入函数时保留多少堆栈,而不会影响它保留的位置。
答案 2 :(得分:0)
这完全取决于您使用的系统的运行时约定,但是,CPU架构通常在决策中扮演重要角色,因为该架构定义了可以安全使用的堆栈管理。例如,在MacOS X下的旧PowerPC上,堆栈帧总是固定大小,在新堆栈帧的低端的堆栈指针的一个原子存储将分配它,解除引用堆栈指针相当于弹出整个堆栈帧。
当前系统如Linux和(纠正我,如果我错了)x86上的Windows有一个更动态的方法,带有原子推送和弹出指令(PowerPC上没有原子弹出),其中函数调用的参数是在每次函数调用之前将其推入堆栈,每次都有效地调整分配的堆栈帧大小。
所以,是的,在许多当前系统上,编译器可以调整堆栈帧的大小,但在其他系统上,这样的操作至少很难实现(尽管从来没有说不出口)。