为什么先推送会减少堆栈指针?

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指向最新的存储值。

我的问题是:首先减少堆栈指针,堆栈的最顶部是不是不可用?如果我们在保存数据之前先减少指针,我们如何将数据存储到堆栈的第一个位置?

是否有理由以这种方式设计堆栈指针?

Eri*_*idt 6

递减堆栈指针,然后将源操作数存储在堆栈顶部。

有一些设计方面的考虑(但请放心,我同意它是相对随意的,因为任何一个都可以工作):

首先,让我们以您的示例为例,看看如果从一开始就将一个 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 似乎更为实际和自然。这是因为(在计算机中,与数学不同)零更像是正数之一而不是负数——例如,考虑无符号数,它总是支持零(以及正值)。


我们还要注意,由于微架构的原因,内存读取比内存写入慢(读取通常在关键路径上,这限制了最大可能的时钟频率,而写入则不然)。因此,后递增弹出(加载)比预递增弹出更受欢迎,因为后递增可以在并行硬件(数据存储器访问)中进行加法,而预递增弹出将加法器放在地址总线和数据存储器读操作的方式。(当然,为了支持后递增弹出,我们需要一个递减前推送。)