为什么先执行push会减少堆栈指针?

时间:2019-07-14 21:48:55

标签: assembly stack cpu-architecture callstack isa

我正试图了解堆栈被推入和拉出时的工作方式,如果问题听起来很简单,则抱歉。

我想从一个超基本的东西开始,例如8位内存(我知道这会过分简化,但让我们开始简单)

我设计堆栈的方式如下:

SP最初将指向内存中的最高位置:0xFF

0xFF:   <- SP

发布推送命令时,我将val保存在SP所指的位置,然后减小SP。

0xFE:         <- SP
0xFF:  val

弹出命令将首先递增SP,然后将SP指向的值移入寄存器。

基本上,我的SP指向堆栈中的第一个可用位置。

但是,这似乎并不是在实际系统中实现的方式。

查看组装手册中的推入说明:

Decrements the stack pointer and then stores the source operand on the top of the stack.

因此,基本上SP指向最新的存储值。

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

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

2 个答案:

答案 0 :(得分:3)

  

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

有一些设计注意事项(但请放心,我同意它是相对任意的,因为任何一种都可以使用):

首先,让我们以您的示例为例,看看如果从头开始将2字节的字压入堆栈会发生什么情况。

        0xFF:   <- SP

push.w val2

        0xFD:         <- SP
        0xFE:  val2(hi 8-bits)   # order depends on big/little endian
        0xFF:  val2(lo 8-bits)

该值的8位到达SP所指向的位置(第一个可用字节),而其他8位必须低于该地址(因为它们不能高于该地址,是吗?)。堆栈指针保留指向空闲字节的位置,因此可以在SP + 1处访问刚刚压入的值。

虽然可以使之可行,但替代方案似乎更合理:

刚推送的项目位于位置SP + 0。

请记住,加载比存储更常见,因此加载堆栈的顶项可能比存储更频繁。在支持负载无位移的体系结构中,以SP + 0访问堆栈顶部有利于加载。 (相对于无人认领的空间,它也主张有偿的空间。)


如果我们想到SP +?作为已声明和未声明的界限,在已声明的空间中包含0似乎更为实际和自然。这是因为(在计算机中,与数学不同,)零更像是一个正数,而不是一个负数-例如,考虑无符号数,该数字始终支持零(以及正值)。


我们还要注意,由于微体系结构的原因,内存读取的速度比内存写入的速度慢(读取通常位于关键路径上,该路径限制了最大可能的时钟频率,而写入并非如此)。因此,后增量弹出(加载)比前增量弹出更可取,因为后增量可以在并行硬件中进行加法(到数据存储器访问),而前增量弹出将加法器放入运算器中。地址总线和数据存储器读取操作的方式。 (当然,要支持后增量弹出,我们需要进行前减推。)

答案 1 :(得分:1)

  

为什么先执行push会减少堆栈指针?

首先:取决于CPU类型,堆栈指针如何工作。

在6800上,先写入值,然后递减堆栈指针。

然后在TMS320F28上写入该值,然后递增堆栈指针。

  

...堆栈顶部不是不可用吗?

请忘记“不可用”一词。正确的词是“使用中”。

考虑以下C或Java程序:

int a, b;
a = someFunction();
someOtherFunction();
thirdFunction(a);

您要将someOtherFunction()的返回值存储在变量中,如下所示:

int a, b;
a = someFunction();
a = someOtherFunction();
thirdFunction(a);

这不是一个好主意,因为变量a已经“在使用中”。变量b仍然是“可用的”。

但是,这不会阻止您覆盖变量a

现在,让我们回到堆栈指针并查看局部变量。查看局部变量(而不是push),我们可以更清楚地看到堆栈指针的实际作用:

输入这样的功能时:

void someFunction(void)
{
    int x, y, z;
    ...
    y = 5;
}

...生成的汇编代码将首先将堆栈指针减少3(假设一个int需要一个内存位置)。

比方说,进入函数之前,堆栈指针的值为0x73。这意味着内存位置0 ... 72未使用,内存位置73 ... FF正在使用。

汇编程序代码会将堆栈指针的值更改为0x70,并将变量x存储在地址0x70,将y存储在地址0x71,将z存储在0x72。

堆栈指针的值为0x70,这意味着内存位置70 ... FF正在“使用中”。

应该清楚的是,内存位置70 ... 72是“使用中”,因为变量xyz存储在此处。

但是,这并不意味着不能访问(读取或写入)这些存储位置:指令y=5;将写入存储位置0x71。