我是汇编语言的初学者,并且注意到编译器发出的 x86 代码即使在发布/优化模式下通常也会保留帧指针,因为它可以将EBP
寄存器用于其他用途。
alloca()
我理解为什么帧指针可能会使代码更容易调试,并且如果在函数中调用它可能是必要的。但是,x86 的寄存器很少,并且使用其中的两个来保存堆栈帧的位置,而一个就足够了,这对我来说没有意义。为什么即使在优化/发布版本中省略帧指针也被认为是一个坏主意?
我是汇编语言的初学者,并且注意到编译器发出的 x86 代码即使在发布/优化模式下通常也会保留帧指针,因为它可以将EBP
寄存器用于其他用途。
alloca()
我理解为什么帧指针可能会使代码更容易调试,并且如果在函数中调用它可能是必要的。但是,x86 的寄存器很少,并且使用其中的两个来保存堆栈帧的位置,而一个就足够了,这对我来说没有意义。为什么即使在优化/发布版本中省略帧指针也被认为是一个坏主意?
帧指针是一个引用指针,允许调试器通过单个常量偏移量知道局部变量或参数的位置。尽管 ESP 的值在执行过程中发生变化,但 EBP 保持不变,从而可以在相同的偏移量处达到相同的变量(例如第一个参数将始终为 EBP+8 而 ESP 的偏移量可能会发生显着变化,因为您将推动/弹出东西)
为什么编译器不丢弃帧指针?因为使用帧指针,调试器可以确定局部变量和参数在哪里使用符号表,因为它们保证在 EBP 的恒定偏移处。否则,没有一种简单的方法可以确定局部变量在代码中的任何位置。
正如 Greg 所提到的,它还有助于调试器的堆栈展开,因为 EBP 提供了堆栈帧的反向链接列表,因此让调试器能够确定函数的堆栈帧(局部变量 + 参数)的大小。
大多数编译器都提供了省略帧指针的选项,尽管它使调试变得非常困难。该选项不应该在全局范围内使用,即使在发布代码中也是如此。您不知道何时需要调试用户的崩溃。
只需将我的两美分添加到已经很好的答案中。
拥有堆栈帧链是良好语言架构的一部分。BP 指向当前帧,其中存储了子程序局部变量。(局部变量为负偏移,参数为正偏移。)
它阻止在优化中使用完美的寄存器的想法提出了一个问题:优化何时何地真正值得?
只有在以下情况下优化才值得:1) 不调用函数,2) 程序计数器花费大量时间,以及 3) 在编译器实际会看到的代码中(即非库函数)。这通常只占整个代码的一小部分,尤其是在大型系统中。
其他代码可以被扭曲和压缩以摆脱循环,这根本不重要,因为程序计数器几乎不存在。
我知道你没有问这个,但根据我的经验,99% 的性能问题与编译器优化完全无关。它们与过度设计有关。
当然,这取决于编译器。我见过 x86 编译器发出的优化代码,它们自由地将 EBP 寄存器用作通用寄存器。(不过,我不记得我注意到哪个编译器了。)
编译器也可以选择维护 EBP 寄存器以帮助在异常处理期间展开堆栈,但这又取决于编译器的精确实现。
但是,x86 的寄存器很少
这仅在操作码只能寻址 8 个寄存器的意义上是正确的。处理器本身实际上将拥有比这更多的寄存器,并使用寄存器重命名、流水线、推测执行和其他处理器流行语来绕过这个限制。维基百科有一个很好的介绍性段落,说明 x86 处理器可以做什么来克服寄存器限制:http ://en.wikipedia.org/wiki/X86#Current_implementations 。
使用堆栈帧在任何硬件中都变得非常便宜,即使是远程现代的。如果您有便宜的堆栈帧,那么保存几个寄存器就不是那么重要了。我确信快速堆栈帧与更多寄存器是一个工程权衡,并且快速堆栈帧获胜。
纯注册可以节省多少钱?这值得么?