为什么 push first 减少堆栈指针?

Why push first decreases the stack pointer?

我试图了解堆栈在推入和拉出某些东西时是如何工作的,如果这个问题听起来很简单,我很抱歉。

我想从一些超级基础的东西开始,比如 8 位内存(我知道这会过于简单化,但让我们从简单开始吧)

我设计堆栈的方式如下:

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

0xFF:   <- SP

当push命令发出时,我会在SP指向的位置保存val,然后减少SP。

0xFE:         <- SP
0xFF:  val

pop 命令会先递增 SP,然后将 SP 指向的值移动到寄存器中。

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

然而,这似乎不是它在实际系统中的实现方式。

查看推送指令的汇编手册:

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

所以基本上SP指向最新的存储值。

我的问题是:先减栈指针,栈顶不是不能用了吗?保存数据前先减指针,如何才能将数据保存到栈首?

有这样设计栈指针的理由吗?

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

有一些设计注意事项(但请放心,我同意它相对随意,因为两者都可以工作):

首先,让我们举个例子,看看如果从一开始就将一个 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 处访问堆栈顶部有利于加载。 (相对于无人认领的 space,它也有利于认领 space。)


如果我们想到SP+?作为claimed 和unclaimed 之间的分界线,在claimed space 中包含0 似乎更为实际和自然。这是因为(在计算机中,与数学不同)零更像是一个正数而不是负数——例如,考虑无符号数,它总是支持零(以及正值)。


我们还要注意,由于micro-architectural原因,内存读取比内存写入慢(读取通常在关键路径上,它限制了最大可能的时钟频率,而写入则不然)。因此,post-increment pop(加载)优于 pre-increment pop,因为 post-increment 可以在并行硬件中进行加法(对数据存储器访问),而 pre-increment pop 在地址总线和数据存储器读取操作的方式中放置了一个加法器。 (当然,要支持 post-increment 流行,我们需要 pre-decrement 推动。)

Why push first decreases the stack pointer?

首先:堆栈指针的工作方式取决于CPU类型。

在6800上,先写值,再减栈指针。

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

... isn't the very top of the stack unusable?

请忘记"unusable"这个词。正确的词是 "in use".

考虑以下 C 或 Java 程序:

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

您想将 someOtherFunction() 的 return 值存储在变量中,如下所示:

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

这不是个好主意,因为变量 a 已经是 "in use"。然而变量 b 仍然是 "useable".

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

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

当输入这样的函数时:

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

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

假设堆栈指针在进入函数之前的值为 0x73。这意味着内存位置 0...72 是 "not in use" 而内存位置 73...FF 是 "in use".

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

堆栈指针的值为 0x70,这意味着内存位置 70...FF 现在是 "in use"。

应该清楚内存位置 70...72 是 "in use",因为变量 xyz 存储在那里。

然而,这并不意味着无法访问(读取或写入)这些内存位置:指令 y=5; 将写入内存位置 0x71。