Assembly 为什么push-first会降低堆栈指针?

Assembly 为什么push-first会降低堆栈指针?,assembly,stack,cpu-architecture,callstack,instruction-set,Assembly,Stack,Cpu Architecture,Callstack,Instruction Set,我试图理解当从堆栈中推拉某个对象时堆栈是如何工作的,如果这个问题听起来很简单,那么很抱歉 我想从一些超基本的东西开始,比如8位内存(我知道这会过于简单,但让我们从简单开始) 我设计堆栈的方法如下: SP最初将指向内存中的最高位置:0xFF 0xFF: <- SP pop命令将首先递增SP,然后将SP指向的值移动到寄存器中 基本上,我的SP指向堆栈中的第一个可用位置 0xFF: <- SP push.w val2 0xFD:

我试图理解当从堆栈中推拉某个对象时堆栈是如何工作的,如果这个问题听起来很简单,那么很抱歉

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

我设计堆栈的方法如下:

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

0xFF:   <- SP
pop命令将首先递增SP,然后将SP指向的值移动到寄存器中

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

        0xFF:   <- SP

push.w val2

        0xFD:         <- SP
        0xFE:  val2(hi 8-bits)   # order depends on big/little endian
        0xFF:  val2(lo 8-bits)
然而,这似乎不是它在实际系统中的实现方式

有关推送说明,请参阅组装手册:

Decrements the stack pointer and then stores the source operand on the top of the stack.
所以基本上SP指向最新的存储值

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

有没有理由这样设计堆栈指针

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

        0xFF:   <- SP

push.w val2

        0xFD:         <- SP
        0xFE:  val2(hi 8-bits)   # order depends on big/little endian
        0xFF:  val2(lo 8-bits)
有一些设计考虑(但请放心,我同意这是相对武断的,因为两者都可以工作):

首先,让我们以您的示例为例,看看如果从一开始就将一个2字节的字推送到堆栈上会发生什么

        0xFF:   <- SP

push.w val2

        0xFD:         <- SP
        0xFE:  val2(hi 8-bits)   # order depends on big/little endian
        0xFF:  val2(lo 8-bits)
0xFF:
为什么push-first会降低堆栈指针

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

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

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

。。。堆栈的最顶端不是不可用吗

请忘记“无法使用”这个词。正确的词应该是“正在使用”

考虑以下C或Java程序:

int a, b;
a = someFunction();
someOtherFunction();
thirdFunction(a);
您希望将
someOtherFunction()
的返回值存储在变量中,如下所示:

int a, b;
a = someFunction();
a = someOtherFunction();
thirdFunction(a);
void someFunction(void)
{
    int x, y, z;
    ...
    y = 5;
}
这不是一个好主意,因为变量
a
已经在“使用中”。但是变量
b
仍然“可用”

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

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

当输入这样的函数时:

int a, b;
a = someFunction();
a = someOtherFunction();
thirdFunction(a);
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“正在使用”,因为变量
x
y
z
存储在那里


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

这两种类型都有实现,有些体系结构支持这两种实现。“第一位置”的概念取决于堆栈类型。您不能写入初始堆栈指针的地址,但这样就不会将其称为“第一个位置”。或者换一种说法:给定一个预期的第一个位置,将堆栈指针设置为紧跟其后的点,然后它就可以工作了。主要是基于观点/太广泛。也就是说,考虑存储的非x86方法,然后减量:第一个有用的数据元素将在偏移<代码> 1代码/代码>或更大的SP。减去然后存储,索引进入有效的堆栈值开始在<代码> 0 /代码>,就像任何类型的基于数组/指针的存储的常规访问一样。这是一个有用的一致性。这还意味着您没有“浪费”偏移量,即
0
的偏移量实际上是有意义的,而不是永远没有用处的。首先修改SP是一种保护。如果堆栈中的数据副本与SP更新之间发生中断,会发生什么情况?如果您第一次更改SP,可能不会出现任何问题。@AlainMerigot:对于没有
推送
指令的ISA来说,这是有意义的,因为您必须手动减少堆栈。但是如果你有一个push指令,那么存储和减量将是原子WRT。打断。(从逻辑上讲,中断总是发生在指令边界上;部分进程被丢弃或中断等待指令完成。)我从未听说过ISA在指令完成一半的情况下,像
push
这样的简单指令是可中断的。所以,满栈和空栈在很大程度上是一种任意的设计选择,但是让SP指向数据是有用的。当然,指令永远不会中断。但是,即使在具有
推送
指令的机器上,也可以使用sp--/store指令对堆栈进行推送,这需要通过首先减小sp来防止中断。虽然从技术上讲,这不是严格要求,对于一个push或一对指令,使用完全相同的机制是有意义的。是否有CPU在地址生成中绕过加法器,从而允许比增量前/空堆栈更低的负载使用延迟?我可以想象,在流水线CPU中,如果加载和pop背靠背运行,则可能会导致资源与使用普通reg+const寻址模式的常规加载冲突。或者这是在一个高度微代码化的非流水线CPU中,比如Z80,其中mayb