我的问题很简单,为什么x86架构使用两个堆栈寄存器(esp ; ebp
)?
堆栈帧的长度已在编译期间确定,然后我认为我们可以使用一个寄存器(例如esp
)来访问堆栈,并保留以前在{的基址{1}}在堆栈上注册(或在其他内存区域注册,但这会导致更多的性能损失)
有可能吗?
答案 0 :(得分:10)
这一切当然取决于调用约定,但通常都是这样。
堆栈指针可以根据您在任何给定时间点推送或弹出堆栈所需的任意内容进行任意移动。这可以在函数内的任何时间发生,因为您需要暂时将一些数据保存在堆栈中。
base 指针通常设置为任何给定堆栈深度的相同值,并用于访问传递的参数(在一侧)和局部变量(在另一侧)。它还用于在退出函数时快速移动堆栈指针。
这样做的原因是简化代码,这样你就不必根据可能改变的堆栈指针来引用堆栈内容。使用基指针可以大大减轻代码生成的任务(您无需知道任何给定时间的堆栈指针是什么,只需使用基本指针,在指定函数期间保持不变)。
没有它,想要推送局部变量的两个副本以调用 next 函数的代码将如下所示:
mov eax, [esp+16] ; get var1
push eax ; push it
mov eax, [esp+20] ; get var1 again
push eax
call _somethingElse
抛开在这种情况下不会重新加载eax
的事实,我想要做的一点是来自移动堆栈指针的项目的相对位置可以不必要地使问题复杂化。
例如,这是一个在程序集中编码的函数,它遵循一个通用的调用约定:
_doSomething:
push ebp ; stack current base pointer
mov ebp, esp ; save previous stack pointer
sub esp, 48 ; incl 48 bytes local storage
; do whatever you want here, including changing
; esp, as long as it ends where it started.
mov esp, ebp ; restore previous stack pointer
pop ebp ; and previous base pointer
ret ; then return
_callIt:
mov eax, 7
push eax ; push parameter for function
call _doSomething
add esp, 4 ; get rid of pushed value
:
如果您遵循代码,您可以看到函数体内的ebp
是一个固定的参考点,[ebp]
(ebp
的内容)是返回地址,{{1是[ebp+4]
的推送值,7
是[ebp-N]
的本地存储空间,其中N从_doSomething
变为1
。
无论在函数体内推送或弹出多少项,都是如此。
答案 1 :(得分:2)
为什么x86架构使用两个堆栈寄存器(esp; ebp)?
根据@ gsg对相关问题的回答" Do any languages / compilers utilize the x86 ENTER instruction with a nonzero nesting level?" x86
架构是在30年前设计的" Pascal machine
"
请参阅The 8086/8088 Primer, by Stephen P. Morse, Chapter 8: High-Level-Language Programming (Pascal)了解其中一位芯片设计师的理由。
因此它集成了对嵌套和递归子例程(过程,函数)的硬件支持,如Pascal programming language中那样structured programming paradigm的一个重要方面应该生成更易于读/写的代码/维护。
以特殊CPU指令形式提供的硬件支持,可以使用较少的指令生成代码。较少的指令通常也意味着更快的代码。
是否可以在不使用ebp寄存器的情况下在另一台Turing complete机器上实现堆栈帧变量访问?
是的,但执行时间不同