x86 栈的使用有 rbp 和 rsp,只设计 rsp 可不可以?为什么?

206 天前
 amiwrong123

https://zhuanlan.zhihu.com/p/656740329

在 x86-64 架构中,rbp 和 rsp 寄存器分别是栈帧基址指针( Base Pointer )和栈指针( Stack Pointer )。

看完了这篇文章,完全搞懂了函数调用过程中,rbp 和 rsp 的使用情况。

但是还是有一点不太理解,就是假如 CPU 只设计 rsp 可以吗?从我简单来看,好像也是可以够用的啊。我总结一下,无外乎是这些:

返回过程:

那从上面分析来看,只有 rsp ,好像也能完成函数调用的工作啊?

1908 次点击
所在节点    程序员
20 条回复
shawnsh
206 天前
怎么取参数
ThirdFlame
206 天前
RSP 可以移动
RBP 是不动的

你去取变量肯定是靠 RBP+偏移来取啊。 靠 RSP 那你还得设计个地方去记录 RSP 的变化,还不如直接设计一个 RBP 呢
amiwrong123
206 天前
@shawnsh #1
你是指,函数调用传参,不是通过寄存器传递;而是通过栈来传递参数的情况呗?
luxor
206 天前
gcc: -fomit-frame-pointer msvc: /Oy
shawnsh
206 天前
@amiwrong123 寄存器才能放多少东西,多次的函数调用不用栈内存保存,你用啥
amiwrong123
206 天前
@shawnsh #1
@ThirdFlame #2
我好像懂你俩的意思了,比如一个函数的汇编里面:
sub $0x10, %rsp
之后取局部变量时,都是用-0x10(%rsp)来取 局部变量的。

是这个意思吧?
amiwrong123
206 天前
@shawnsh #5
嗯,确实。
很多时候,可能必须用栈 来传递参数。这个时候,只有 rbp 记录了 上一个函数的栈帧基地址(刚进入下一个函数时),如果没有 rbp 就不好办了还。
amiwrong123
206 天前
@amiwrong123 #6
这里写了,比如一个函数的汇编里面:
sub $0x30, %rsp
之后取局部变量时,都是用-0x10(%rbp)、-0x20(%rbp)、-0x30(%rbp)来取 各个局部变量的。
amiwrong123
206 天前
这里写错了
vituralfuture
206 天前
不要 rbp 是可行的,只不过追溯函数调用栈变得困难

不要 rbp 的时候,编译器知道每个函数分配的栈帧大小,但是没有保存起来,例如函数返回时需要恢复 rbp ,而 rbp 就保存在当前 rbp 指向的地址,这时 rsp 减去编译器知道的栈帧大小就能得到 rbp

call 指令会将 pc(指向下一条指令)和 rbp 压栈,这样子程序能够恢复栈帧,并回到函数调用发生的位置的下一条指令,如果要追溯函数调用栈,只需要拿到 rbp-8 指向的返回地址。读取 rbp 可以使用内联汇编。这就需要知道 rbp 的值,而这个值编译器知道却没有保存

gcc 有个参数-fomit-frame-pointer ,就是省略了 rbp 的使用,但不难以调试程序
adoal
206 天前
别说是没有 BP 可以,就算没有 SP 也不是不可以的
adoal
206 天前
从正常的 C 编译成 X86 汇编的代码来看,为了维护栈帧而对 BP 的所有操作,其实都是显式的,所以实际上就是个普通寄存器,只不过出于约定,用 BP 来做这事就是了,没有 BP ,也可以拿其它寄存器用。

SP 就比 BP 特殊了,call/ret/push/pop 等指令要隐式操作 SP……但实质上无非就是移动 SP 和读写数值而已,这些指令的设计是因为对应的操作太常见了,所以就为之做了优化,设计出专用指令,但从指令集完整性的角度完全可以编译器拆开做,SP 的功能也可以由任意通用寄存器来承担,在很多 RISC 设计里就是约定到某个通用寄存器。只有 IP (一般叫 PC 的更多)才是无论如何也不可能通用化的。
ysc3839
206 天前
x86 bp 只是叫 base pointer 吧,跟 stack 无关,用户想怎么使用都可以。sp 是真的 stack pointer ,push pop 等操作会影响 sp 。
amiwrong123
206 天前
@vituralfuture #10
谢谢。这就是我想要的答案。
也就是说,默认编译出来的汇编,每个函数都会去使用 rbp 的。
但是如果加了-fomit-frame-pointer 参数,那么每个函数就不会去使用 rbp 了。

所以结论就是:函数调用过程中,可以不使用 rbp 。
amiwrong123
206 天前
@adoal #12
嗯嗯,你的主要意思是:
如果没有 BP ,那么用其他 通用寄存器来当 BP 来用,也是一样的。

在 RISC 里,甚至 SP 都可以用 其他通用寄存器 来代替。
amiwrong123
206 天前
@vituralfuture #10
不过 1 楼那个问题,要是 函数调用时参数是通过栈来传递参数的话(不然可以直接通过寄存器传参),没有 rbp 的话,会不会有点不好办。

不过我想,有 rbp 的话(在刚进入函数时),就是 rbp 减一个数来 获得传参;如果没有 rbp ,那就通过 rsp 加一个数 来获得传参。好像一样能解决问题。
PTLin
206 天前
rbp 主要就是为了 backtrack ,假如完全不需要这个功能就可以取消 push rbp,mov rbp rsp 什么的,在 Linux 内核中完全不需要 ftrace 什么的功能也可以编译成不用 rbp 的。
sMil3
206 天前
mips 就是按 sp 找局部变量吧
iceheart
206 天前
rbp 是链表指针,
这个链表就是 callstack 链表
kingly
205 天前
基础不行真的不建议思考这些问题,没啥用,纯浪费精力。你调试的时候看堆栈回溯,没有 rbp 你看个屁啊,栈底上面保存了返回地址,除此之外还有很多其他小用处,比如优化了寻找速度,这都是用处。建议基础没砸实之前不要去产生过多疑问,浪费人生。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/1045900

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX