A i*_*ion 1 assembly stack pointers x86-64
我很困惑为什么如果你想在堆栈上分配 8 字节存储,你使用以下命令
subq $8,%rsp
Run Code Online (Sandbox Code Playgroud)
%rsp 存储一个地址。为什么从 %rsp 中存储的地址中减去文字值 8 会分配 8 个字节?特别是,为什么$x对应x个字节?
编辑:我的问题在此stackoverflow 回复中得到了解答。它以字节为单位,因为这是 int 的大小,而 int 是传入的文字参数。
编辑2:以上是不正确的。参见博的评论。
你说得对。堆栈指针只是指向内存位置的指针,即它保存一个有效地址的整数值。该内存块实际上在线程初始化时被分配为一个大块。它通常足够大以避免堆栈溢出。堆栈指针在开始时指向该块的末尾,并且每当压入新值时就会递减(每当弹出时就会递增)。将值压入堆栈只是将给定值移动到 RSP 指向的内存插槽并递减它。例如:
push rax
Run Code Online (Sandbox Code Playgroud)
与此同义(除了不影响 FLAGS):
sub rsp, 8
mov [rsp], rax
Run Code Online (Sandbox Code Playgroud)
仅从 RSP 中减去(而不是执行push)只会在堆栈中留下可用空间,您可以将其用于您自己的目的,而不会覆盖那里的任何旧值,并且大小可以大于一个 8 字节堆栈插槽1。这就是局部变量实际上的工作原理。所以:
sub rsp, 16
Run Code Online (Sandbox Code Playgroud)
将堆栈指针向下移动 16 个字节,以便您有 16 个字节的空间,您可以在自己的函数中使用任意大小的组合。要释放它,您需要记住rsp相应地增加寄存器,或者使用帧指针,例如:
mov rbp, rsp
sub rsp, 16
; and here access the values using rbp-xxx instead of rsp+xxx
mov rsp, rbp
Run Code Online (Sandbox Code Playgroud)
因此编译器可以代替push raxa sub rsp, 8,但这实际上需要写入内存(因为需要存储 rax 的内容)。那将是一种浪费。相反,在真正需要该内存块之前,移动指针本身是一种更便宜的操作。
(让我知道我在这里使用的英特尔汇编语法是否会造成混乱,我只是发现它更容易编写)
脚注 1:8 字节的倍数是保持堆栈对齐的好主意。事实上,调用约定让被调用者假设 RSP 在 a 之前按 16 对齐call,因此调用者需要这样做,除非调用不依赖于此的您自己的函数。a 之前的 RSP%16==0call意味着 a 之后的 RSP%16==8 call(推送返回地址)。计算 和push,sub rsp自函数入口以来 RSP 的总移动量应为 8 的奇数倍。
一些编译器将push rax在函数序言中使用来移动 RSP,因为这比sub rsp, 8在某些 CPU 上更有效,即使它执行我们不关心的存储。(为什么这个函数第一个操作要把RAX压入栈?)。就该空间中的价值而言,新存放的垃圾与旧垃圾是等价的;如果您想变得聪明,push 0或者想要作为您要保留的初始化程序实际上有用的东西,而不是稍后mov。(什么C/C++编译器可以使用push pop指令来创建局部变量,而不是仅仅增加esp一次?)。