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