这不是由指令集架构指定的,就 ISA 而言,堆栈可以是满的或空的,降序或升序 - RISC V 指令集不支持特定的排列。这些术语(full/empty、ascending/descending)在 ARM 指令集中常用:full 意味着堆栈指针指向一个占用/非空闲位置,而 empty 意味着堆栈指针指向第一个可用字。
但是,堆栈方向和堆栈指针是指第一个正在使用的字还是第一个空闲字是由 RISC V 调用约定指定的,它是 ABI(应用程序二进制接口)的一部分)。
RISC V 的标准调用约定像 MIPS 一样使用堆栈,这是一种完全降序的方法,这意味着堆栈指针指向正在使用的最后一个字,并且空闲内存的地址低于堆栈中的当前值指针寄存器。
如果你想使用堆栈空间,那么减少堆栈指针,新堆栈指针值和旧堆栈指针值之间的新内存是你使用的;只需在返回给调用者之前释放它,以便调用者具有与它给您相同的堆栈指针值。 (也不要写入初始未递减指针指向的内存,因为它指向的位置及以上都被认为是在使用中。)
因此,初始堆栈指针可以指向刚刚超过堆栈末尾的单词,该单词不能使用,也不会被预期使用,软件应该首先递减,以找到要使用的堆栈空间。
发件人:https://github.com/riscv/riscv-elf-psabi-doc/blob/master/riscv-elf.md
堆栈向下增长(朝向低地址)并且堆栈指针应在过程进入时与 128 位边界对齐。堆栈上传递的第一个参数位于函数入口处堆栈指针的偏移量零处;以下参数存储在相应的更高地址。
还有:
程序不得依赖于地址位于堆栈指针下方的堆栈分配数据的持久性。
只要说相对于堆栈指针寄存器中保存的地址的偏移量 0 属于调用者,就谁拥有内存(因此负责释放该内存)而言,无论是否实际传递任何参数就足够了在堆栈上。 (如果栈上传入了一个或多个参数,则允许被调用者随意修改这些内存值,但内存及其释放仍属于调用者;显然,被调用者不得修改非参数内存位于或高于堆栈指针。)
要在堆栈上分配单个字,首先减少堆栈指针寄存器sp,然后使用相对于sp 的位置0。其他调用者将保留此内存。返回时,sp 应返回到其在函数入口处保存的原始值。
通常在 MIPS 和 RISC V 上,当需要堆栈空间时,汇编语言程序员或编译器将计算所有本地存储和执行函数调用(即多余参数)所需的最大堆栈空间,并分配该数量在序言中的单个指令中分配空间,在结尾中使用单个指令释放空间。
调用者将在序言中仅分配一次堆栈空间,并在结尾时仅释放一次,而不是反复递减堆栈指针寄存器。
(RISC V 也没有提供red-zone,对于其他 ABI,它是堆栈指针下方的一些空间,保证在没有信号处理程序或其他任何异步覆盖它的情况下可用。)
一些模拟器以奇怪的方式管理堆栈空间(例如更改堆栈指针寄存器值 - 而不是实际写入堆栈空间,这更像是硬件的工作方式),因此这些模拟器选择浪费一个字,而不是让初始堆栈指针引用不同页面/部分上的内存地址,甚至可能引用不存在的内存,即使在模拟器上运行的软件应该在使用堆栈空间之前递减sp,这会将堆栈指针带到有效的内存区域,因此实际上只有有效的内存被访问。