问题描述
输入功能,标准序言
push rbp
mov rbp, rsp
sub rsp, 128 ; large space for storing doubles, for example
如何现在通过rsp +正偏移量或通过rbp +负偏移量引用局部变量?
How to reference local variables now, via rsp + positive offset, or via rbp + negative offset?
阅读 https://en.wikibooks.org/wiki/X86_Disassembly/Functions_and_Stack_Frames ,的确是可以理解的.它写
Reading https://en.wikibooks.org/wiki/X86_Disassembly/Functions_and_Stack_Frames, indeed quite understandable.It writes
... esp的值不能可靠地用于确定(使用适当的偏移量)特定局部变量的存储位置.为了解决这个问题,许多编译器使用ebp寄存器中的负偏移量来访问局部变量.
为什么不可靠?在这个问题之前,我是通过rsp访问局部变量的,如下所示:
Why not reliable? Until that question I was accessing local variables via rsp, like this:
mov rax, [rsp+.length] ; get length of array
mov [rsp+8], rax ; store sum at the stack
使用rsp进行堆栈引用一切都很好.
everything goes quite nicely using rsp for stack referencing.
推荐答案
查看gcc输出.优化时默认为-fomit-frame-pointer
,仅在函数使用可变长度数组或需要将堆栈对齐到大于16B时制作堆栈帧.
Look at gcc output. It defaults to -fomit-frame-pointer
when optimizing, only making a stack frame when functions use variable-length arrays or it needs to align the stack to more than 16B.
该Wiki页面基本上是错误的.没有可怕的怪异事物使它不可靠".唯一无法做到的是,当您需要以非汇编时间常数的量修改RSP时.
That wiki page is basically wrong. There's no scary weird stuff that makes it "unreliable". The only time you can't do it is when you need to modify RSP by an amount that isn't an assemble-time constant.
但是,如果 do 用push rbp
/mov rbp, rsp
创建堆栈帧,则应该使用RBP相对寻址模式.效率更高,因为[rsp + 8]
需要一个额外的字节来编码(与[rbp - 8]
相比).以RSP为基址的寻址模式始终需要一个SIB字节,即使没有索引寄存器也是如此.
However, if you do make a stack frame with push rbp
/ mov rbp, rsp
, you should use RBP-relative addressing modes. It's more efficient, because [rsp + 8]
takes an extra byte to encode (vs. [rbp - 8]
). Addressing modes with RSP as the base register always need a SIB byte, even when there's no index register.
使用RSP相对寻址模式的要点是您可以避免浪费指令来制作堆栈帧,因此RBP只是另一个可以保存/恢复的调用保存寄存器(如RBX)并用于您想要的任何东西.
The point of using RSP-relative addressing modes is that you can avoid wasting instructions making a stack frame, so RBP is just another call-saved register (like RBX) that you can save/restore and use for whatever you want.
RBP相对寻址的另一个大优点是,从RBP到给定变量的偏移量对于整个函数保持恒定.与编译器不同,我们微不足道的人很容易被在函数内部更改RSP的push和pop所迷惑.当然,因为两个ABI都在寄存器中传递arg,所以64位代码几乎不会在序言和尾声之间的函数内更改RSP.在序言/结尾中保存/恢复某些保留呼叫的寄存器(例如RBX或R12-R15)通常比在函数内部具有push/pop更好(并且绝对比在循环内部更好).当您需要溢出/重新加载时,通常最好使用mov
进行随机访问.
The other big advantage to RBP-relative addressing is that the offset from RBP to a given variable stays constant for the entire function. Unlike compilers, we puny humans are easily confused by pushes and pops which change RSP inside a function. Of course, 64-bit code hardly ever changes RSP inside a function between the prologue and epilogue, because both ABIs pass args in registers. Saving/restoring some call-preserved registers (like RBX or R12-R15) in the prologue/epilogue is often better than having push/pop inside the function (and definitely better than inside a loop). When you need to spill/reload, mov
for random access is usually best.
在32位代码中,用手写代码制作堆栈框架通常更有意义,尤其是.为了可维护性.在64位代码中,这通常不是什么大问题.尽管使用一对额外的push/pop保存/恢复一个额外的寄存器确实会改变堆栈布局,但这对是否在堆栈上传递了任何args都很重要(例如,按值构造一个大的struct,但是编写函数以使用const指针)而不是arg!).
In 32-bit code, making a stack frame in hand-written code often makes more sense, esp. for maintainability. In 64-bit code, it's usually not much of an issue. Although saving/restoring an extra register with an extra pair of push/pop does change the stack layout, which does matter if any args were passed on the stack (e.g. a large struct by value, but write your function to take a const-pointer arg instead!).
这篇关于如何正确引用堆栈上的局部变量的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!