这篇文章涉及到Intel平台上BP/EBP/RBP寄存器的相关内容。该寄存器默认指向堆栈段(不需要特殊前缀来访问堆栈段)。
对于在堆栈中访问数据结构、变量和动态分配工作空间,EBP是最好的寄存器选择。EBP通常用于相对于堆栈上的固定点而不是相对于当前TOS访问堆栈上的元素。它通常标识为为当前过程建立的当前堆栈帧的基地址。当EBP被用作偏移量计算中的基础寄存器时,偏移量会自动计算在当前堆栈段内(即由SS当前选择的段)。因为SS不必明确指定,在这种情况下指令编码更加有效率。EBP还可用于索引通过其他段寄存器寻址的段。
(来源-http://css.csail.mit.edu/6.858/2017/readings/i386/s02_03.htm)
由于在大多数32位平台上,数据段和堆栈段是相同的,所以EBP/RBP与堆栈的关联已经不再是一个问题。同样,在64位平台上也是如此:x86-64架构由AMD在2003年引入时,在64位模式下大大降低了对分段的支持:强制四个段寄存器CS、SS、DS和ES为0。这些情况意味着,在x86 32位和64位平台上,EBP/RBP寄存器可以在访问内存的处理器指令中使用,而无需任何前缀。
因此,您提到的编译器选项允许将BP/EBP/RBP用于其他用途,例如保存本地变量。
“这避免了保存、设置和恢复帧指针的指令”是指避免每个函数进入时以下代码:
push ebp
mov ebp, esp
enter
指令在Intel 80286和80386处理器上非常有用。
在函数返回之前,使用以下代码:
mov esp, ebp
pop ebp
或者使用 leave
指令。
调试工具可以扫描堆栈数据并使用这些推送的 EBP 寄存器数据来定位 调用点
,即按层次结构显示调用函数名称和参数的顺序。
程序员可能会对堆栈帧有疑问,不是广义上(它是在堆栈中仅为一个函数调用提供服务并保留返回地址、参数和本地变量的单个实体)而是狭义上——当编译器选项的上下文中提到 堆栈帧
一词时。从编译器的角度看,堆栈帧只是例行程序的入口和出口代码,它将锚点推到堆栈上——这也可用于调试和异常处理。调试工具可以扫描堆栈数据并使用这些锚点进行回溯,同时在堆栈中定位 调用点
,即以与按层次结构调用它们的相同顺序显示函数名称。
这就是为什么理解堆栈帧对于程序员来说在编译器选项方面意味着什么非常重要——因为编译器可以控制是否生成此代码。
在某些情况下,编译器可以省略堆栈帧(例行程序的入口和出口代码),并且变量将直接通过堆栈指针(SP/ESP/RSP)而不是方便的基指针(BP/ESP/RSP)进行访问。
编译器省略某些函数的堆栈帧(例行程序的入口和出口代码)的条件可能有所不同,例如:(1) 函数是叶子函数(即不调用其他函数的末端实体);(2) 不使用异常;(3) 不使用传递参数的例行程序;(4) 函数没有参数。
省略堆栈帧(例行程序的入口和出口代码)可以使代码更小更快。但它们也可能对调试工具追踪堆栈数据并将其显示给程序员的能力产生负面影响。这些是决定函数在何种条件下应满足才能被编译器授予堆栈帧入口和出口代码的编译器选项。例如,编译器可能有选项,以在以下情况下向函数添加此类入口和出口代码:(a) 总是,(b) 从不,(c) 在需要时(指定条件)。
从普遍性返回特殊性:如果您使用 -fomit-frame-pointer
GCC 编译器选项,则可能在例行程序的入口和出口代码上都取得胜利,并获得一个附加寄存器(除非它已经默认打开,或者由其他选项隐含地打开,在这种情况下,使用 EBP/RBP 寄存器并且显式指定此选项将不会获得额外的收益)。但请注意,在 16 位和 32 位模式下,BP 寄存器没有像 AX 那样提供访问其 8 位部分(AL 和 AH)的能力。
该选项不仅允许编译器在优化中使用EBP作为通用寄存器,还防止为堆栈帧生成退出和进入代码,从而使调试更加复杂。因此,GCC文档明确声明(强调选项会使某些机器上的调试变得不可能)。
请注意,与调试或优化相关的其他编译器选项可能会隐式打开或关闭-fomit-frame-pointer
选项。
我没有在gcc.gnu.org找到关于其他选项如何影响x86平台上的-fomit-frame-pointer
的官方信息,只有https://gcc.gnu.org/onlinedocs/gcc-3.4.4/gcc/Optimize-Options.html中写到:
-O还会在不影响调试的情况下在某些机器上打开-fomit-frame-pointer。
因此,从文档本身来看,不清楚是否在x86平台上只使用单个“-O”选项进行编译时是否会打开-fomit-frame-pointer
。这可以通过经验测试,但在这种情况下,GCC开发人员没有承诺不会未经通知更改此选项的行为。
但是,Peter Cordes在评论中指出了在x86-16平台和x86-32/64平台之间的默认设置的-fomit-frame-pointer
的差异。
这个选项-fomit-frame-pointer
也与Intel C ++编译器15.0相关,而不仅仅是GCC:
对于Intel编译器,该选项有一个别名/Oy
。
以下是Intel对此的描述:
这些选项确定是否在优化中使用EBP作为通用寄存器。选项-fomit-frame-pointer和/Oy允许此使用。选项-fno-omit-frame-pointer和/Oy-禁止它。
一些调试器希望EBP用作堆栈帧指针,如果不这样做,它们就无法生成堆栈回溯。选项-fno-omit-frame-pointer和/Oy-指导编译器为所有函数生成并使用EBP作为堆栈帧指针,以便调试器可以生成堆栈回溯而无需进行以下操作:
对于-fno-omit-frame-pointer选项:在使用-O0选项或-g选项时关闭优化。
对于/Oy-选项:关闭/O1、/O2或/O3优化。
当使用-O1、-O2或-O3选项时,设置-fomit-frame-pointer选项。当使用-O0或-g选项时,设置-fno-omit-frame-pointer选项。
当使用/O1、/O2或/O3选项时,设置/Oy选项。当使用/Od选项时,设置/Oy-选项。
使用-fno-omit-frame-pointer或/Oy-选项会减少1个可用的通用寄存器,并可能导致代码效率稍微降低。
注:针对Linux*系统:GCC 3.2存在异常处理问题,因此,在安装C++和打开异常处理(默认情况下)的情况下,英特尔编译器会忽略此选项。
请注意,上述引文仅适用于英特尔C++ 15编译器,而非GCC。
Release
和Debug
,实际上非常有用,以这个选项为例。 - Kotauskasalloca
,一个答案也是如此。 - Peter Cordes