为什么 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",因为变量 x
、y
和 z
存储在那里。
然而,这并不意味着无法访问(读取或写入)这些内存位置:指令 y=5;
将写入内存位置 0x71。
我试图了解堆栈在推入和拉出某些东西时是如何工作的,如果这个问题听起来很简单,我很抱歉。
我想从一些超级基础的东西开始,比如 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",因为变量 x
、y
和 z
存储在那里。
然而,这并不意味着无法访问(读取或写入)这些内存位置:指令 y=5;
将写入内存位置 0x71。