Sem*_*aki 3 assembly stack callstack instruction-set cpu-architecture
我试图了解当某些东西被推入和拉出时堆栈是如何工作的,如果问题听起来很简单,那么抱歉。
我想从一些超级基础的东西开始,比如 8 位内存(我知道这会过于简单,但让我们从简单开始)
我设计堆栈的方式如下:
SP 最初将指向内存中的最高位置:0xFF
0xFF: <- SP
Run Code Online (Sandbox Code Playgroud)
当发出push命令时,我会保存val在SP指向的位置,然后减少SP。
0xFE: <- SP
0xFF: val
Run Code Online (Sandbox Code Playgroud)
pop 命令将首先增加 SP,然后将 SP 指向的值移入寄存器。
基本上我的 SP 指向堆栈中的第一个可用位置。
然而,这似乎不是它在实际系统中的实现方式。
查看组装手册中的推送说明:
Decrements the stack pointer and then stores the source operand on the top of the stack.
Run Code Online (Sandbox Code Playgroud)
所以基本上SP指向最新的存储值。
我的问题是:首先减少堆栈指针,堆栈的最顶部是不是不可用?如果我们在保存数据之前先减少指针,我们如何将数据存储到堆栈的第一个位置?
是否有理由以这种方式设计堆栈指针?
递减堆栈指针,然后将源操作数存储在堆栈顶部。
有一些设计方面的考虑(但请放心,我同意它是相对随意的,因为任何一个都可以工作):
首先,让我们以您的示例为例,看看如果从一开始就将一个 2 字节的字压入堆栈会发生什么。
0xFF: <- SP
push.w val2
0xFD: <- SP
0xFE: val2(hi 8-bits) # order depends on big/little endian
0xFF: val2(lo 8-bits)
Run Code Online (Sandbox Code Playgroud)
该值的 8 位到达 SP 指向的位置(第一个可用字节),而其他 8 位必须低于该地址(因为它们不能高于该地址,嗯?)。堆栈指针指向一个空闲字节,所以刚压入的值可以在 SP + 1 处访问。
虽然这可以起作用,但替代方案似乎更合理:
刚刚推送的项目位于位置 SP + 0。
请记住,加载比存储更常见,因此加载堆栈的顶部项目可能比存储它更频繁地发生。在支持无位移加载的架构中,在 SP + 0 处访问堆栈顶部有利于加载。(它也有利于声称的空间而不是无人认领的空间。)
如果我们想到 SP + ? 作为已声明和未声明的分界线,在已声明的空间中包含 0 似乎更为实际和自然。这是因为(在计算机中,与数学不同)零更像是正数之一而不是负数——例如,考虑无符号数,它总是支持零(以及正值)。
我们还要注意,由于微架构的原因,内存读取比内存写入慢(读取通常在关键路径上,这限制了最大可能的时钟频率,而写入则不然)。因此,后递增弹出(加载)比预递增弹出更受欢迎,因为后递增可以在并行硬件(数据存储器访问)中进行加法,而预递增弹出将加法器放在地址总线和数据存储器读操作的方式。(当然,为了支持后递增弹出,我们需要一个递减前推送。)