EBP帧指针寄存器的用途是什么?


94

我是汇编语言的初学者,并且注意到编译器发出的x86代码即使在释放/优化模式下也可以将EBP寄存器用于其他用途时,通常也会保持帧指针。

我理解为什么帧指针可能使代码更易于调试,并且如果alloca()在函数中调用它可能是必需的。但是,x86的寄存器非常少,当其中一个寄存器足够用时,使用其中两个寄存器来保持堆栈帧的位置对我来说是没有意义的。为什么即使在优化/发布版本中也忽略帧指针还是​​一个坏主意?


19
如果您认为x86的寄存器很少,则应检查6502 :)
Sedat Kapanoglu,2009年


1
C99 VLA也可以从中受益。
Ciro Santilli郝海东冠状病六四事件法轮功


1
帧指针不会使堆栈指针冗余吗?。TL; DR:1.非平凡的堆栈对齐2.堆栈分配(alloca3 .运行时易于实现:异常处理,沙箱,GC
Alexander Malakhov

Answers:


102

帧指针是一个引用指针,它使调试器可以通过单个常量偏移量知道局部变量或参数在何处。尽管ESP的值在执行过程中会发生变化,但是EBP保持不变,因此可以在相同的偏移量处到达相同的变量(例如,第一个参数将始终为EBP + 8,而ESP的偏移量可能会发生显着变化,因为您将按/弹出东西)

为什么编译器不丢弃帧指针?因为有了帧指针,调试器可以确定局部变量和参数在哪里使用符号表,因为可以保证局部变量和参数与EBP保持恒定的偏移量。否则,没有一种简单的方法可以确定局部变量在代码中任何位置的位置。

如Greg所述,由于EBP提供了堆栈帧的反向链接列表,因此它还有助于调试器展开堆栈,因此使调试器可以计算出函数的堆栈帧的大小(局部变量+自变量)。

大多数编译器都提供了省略帧指针的选项,尽管它使调试工作非常困难。即使在发行代码中,也不应在全局范围内使用该选项。您不知道何时需要调试用户的崩溃。


10
编译器可能知道它对ESP的作用。其他积分有效,但是+1
erikkallen

8
现代调试器甚至可以在使用编译的代码中进行堆栈回溯-fomit-frame-pointer。该设置是最近gcc中的默认设置。
彼得·科德斯

2
@SedatKapanoglu:数据部分记录了必要的信息:yosefk.com/blog/…–
彼得·科德斯

3
@SedatKapanoglu:该.eh_frame_hdr部分也用于运行时异常。您会objdump -h在Linux系统上的大多数二进制文件中找到它(带有),对于Linux,它约为16k /bin/bash,而对于GNU /bin/true,它约为572B,对于Linux,它约为108k ffmpeg。有一个gcc选项可以禁用它的生成,但这是一个“普通”数据部分,而不是strip默认情况下会删除的调试部分。否则,您将无法通过没有调试符号的库函数回溯。该部分可能比push/mov/pop它替换的指令大,但运行时成本(例如uop缓存)几乎为零。
彼得·科德斯

3
关于“例如第一个参数将始终位于EBP-4”:EBP + 8(在x86上)不是第一个参数吗?
艾登·K。

31

只需在已经不错的答案中加上我的两分钱即可。

拥有一堆堆栈框架是好的语言体系结构的一部分。BP指向当前帧,在该帧中存储子例程局部变量。(局部变量为负偏移量,参数为正偏移量。)

阻止完美的寄存器用于优化的想法提出了一个问题:优化在何时何地真正值得?

仅在以下情况的优化循环中值得优化:1)不调用函数; 2)程序计数器花费其大部分时间,以及3)编译器实际会看到的代码(即非库函数)。这通常只占整体代码的一小部分,尤其是在大型系统中。

可以扭曲和压缩其他代码以摆脱循环,这根本没有关系,因为程序计数器实际上从不存在。

我知道您没有问这个问题,但是根据我的经验,99%的性能问题与编译器优化完全无关。他们与过度设计有关。


谢谢@Mike,我发现您的回答非常有帮助。
stytyfootersdude”,2010年

2
取消框架指针还可以为每个函数调用节省几条指令,这本身就是一个很小的优化。顺便说一句,您对“乞求问题”的使用不正确;你的意思是“提出问题”。
augurar 2014年

@augurar:固定。谢谢。我自己有点语法上的脾气:)
Mike Dunlavey 2014年

3
@augurar语言演变:“求问”现在仅表示“提出问题”。成为过时用法的规定主义者,这并没有增加什么。
user3364825 2014年

9

当然,这取决于编译器。我已经看到x86编译器发出的优化代码,它们自由地将EBP寄存器用作通用寄存器。(不过,我不记得使用哪个编译器了。)

编译器还可以选择维护EBP寄存器,以帮助在异常处理期间展开堆栈,但这再次取决于精确的编译器实现。


大多数编译器默认-fomit-frame-pointer为启用优化时。(在ABI允许的情况下)。即使针对32位Windows,GCC,clang,ICC和MSVC都可以执行IIRC。是的,我的回答是:为什么使用ebp比使用esp寄存器更好地在堆栈上定位参数?表明即使32位Windows也可以省略帧指针。32位x86 Linux绝对可以并且可以做到。当然,64位ABI从一开始就允许省略帧指针。
彼得·科德斯

4

但是,x86的寄存器很少

这仅在操作码只能寻址8个寄存器的意义上是正确的。处理器本身实际上将拥有更多的寄存器,并使用寄存器重命名,流水线,推测性执行和其他处理器流行语来绕过该限制。Wikipedia很好地介绍了x86处理器可以克服寄存器限制的方法:http : //en.wikipedia.org/wiki/X86#Current_implementations


1
最初的问题是关于生成的代码,它严格限于操作码可引用的寄存器。
达伦(Darron),

1
是的,但这就是为什么在如今的优化版本中省略帧指针并不那么重要的原因。
迈克尔

1
寄存器重命名与实际拥有大量可用寄存器并不完全相同。在很多情况下,寄存器重命名将无济于事,但更多的“常规”寄存器将有帮助。
jalf

1

在任何硬件中,即使是远程的现代设备,使用堆栈框架都变得非常便宜。如果您有便宜的堆栈帧,那么保存几个寄存器就没有那么重要了。我敢肯定,快速堆栈帧与更多寄存器是一个工程上的权衡,并且可以快速堆栈帧获胜。

您要存多少钱去纯寄存器?这值得么?


更多寄存器受指令编码的限制。x86-64使用REX前缀字节中的位将src和dest寄存器的指令的寄存器指定部分从3位扩展到4位。如果有空间,x86-64可能会分配给32个体系结构寄存器,尽管保存/恢复很多上下文开关会开始增加。15比7有了巨大的进步,但在大多数情况下31的改进要小得多。(不将堆栈指针算作通用指针。)快速推入/弹出操作不仅对堆栈帧有用,而且对更多人而言非常有用。不过,这与#regs并不是一个折衷。
彼得·科德斯
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.