Architecture 为什么堆栈通常向下生长?

Architecture 为什么堆栈通常向下生长?,architecture,stack,history,Architecture,Stack,History,我知道,在我个人熟悉的体系结构(x86、6502等)中,堆栈通常向下增长(即,推到堆栈上的每个项目都会导致SP递减,而不是递增) 我想知道这其中的历史原因。我知道,在一个统一的地址空间中,在数据段的相对端启动堆栈是方便的(比如说),所以如果中间的两个面发生碰撞,则只有一个问题。但为什么堆栈传统上会占据顶部呢?特别是考虑到这是如何与“概念”模型相反 (请注意,在6502体系结构中,堆栈也向下增长,即使它被限制在一个256字节的页面上,而且这个方向选择似乎是任意的。)IIRC堆栈向下增长,因为堆向上

我知道,在我个人熟悉的体系结构(x86、6502等)中,堆栈通常向下增长(即,推到堆栈上的每个项目都会导致SP递减,而不是递增)

我想知道这其中的历史原因。我知道,在一个统一的地址空间中,在数据段的相对端启动堆栈是方便的(比如说),所以如果中间的两个面发生碰撞,则只有一个问题。但为什么堆栈传统上会占据顶部呢?特别是考虑到这是如何与“概念”模型相反


(请注意,在6502体系结构中,堆栈也向下增长,即使它被限制在一个256字节的页面上,而且这个方向选择似乎是任意的。)

IIRC堆栈向下增长,因为堆向上增长。这可能是另一种情况。

我听到的一个很好的解释是,过去有些机器只能有无符号偏移,因此,您希望堆栈向下增长,这样就可以命中局部变量,而不必丢失额外的指令来伪造负偏移量。

一个可能的原因可能是它简化了对齐。如果在堆栈上放置一个必须放置在4字节边界上的局部变量,只需从堆栈指针中减去对象的大小,然后将两个较低的位归零即可获得正确对齐的地址。如果堆栈向上增长,确保对齐就变得有点棘手。

至于历史原因,我不能肯定(因为我没有设计它们)。我的想法是,早期的CPU将其原始程序计数器设置为0,自然希望在另一端启动堆栈并向下增长,因为它们的代码自然向上增长

另请注意,复位时将程序计数器设置为0并不适用于所有早期CPU。例如,摩托罗拉6809将从地址
0xfffe/f
获取程序计数器,因此您可以在任意位置开始运行,具体取决于该地址提供的内容(通常但不限于ROM)

一些历史系统首先要做的事情之一是从顶部扫描内存,直到找到一个可以读回写入的相同值的位置,这样就可以知道实际安装的RAM(例如,一个带有64K地址空间的z80不一定有64K或RAM,事实上,在我早期64K的内存会很大)。一旦找到最上面的实际地址,它将适当地设置堆栈指针,然后可以开始调用子例程。作为启动的一部分,这种扫描通常由运行ROM中代码的CPU完成


关于堆栈的增长,并非所有堆栈都向下增长,请参阅以了解详细信息。

我认为这纯粹是一个设计决策。并不是所有的堆栈都向下增长——请参阅,以获得关于不同体系结构上堆栈增长方向的一些好讨论。

我不确定,但我以前为VAX/VMS做过一些编程。我似乎记得内存的一部分(堆???)在上升,而堆栈在下降。当这两个人相遇时,你的记忆就没有了。

我相信这个惯例是从IBM 704及其臭名昭著的“减量寄存器”开始的。现代语音将其称为指令的偏移字段,但关键是它们向下,而不是向上。

再向下2厘米:

除了所有提到的历史原因之外,我非常确定在现代处理器中没有任何理由是有效的。所有处理器都可以使用有符号偏移量,并且自从我们开始处理多线程以来,最大化堆/堆栈距离就没有什么意义了

我个人认为这是一个安全设计缺陷。比如说,如果x64体系结构的设计者扭转了堆栈增长的方向,那么大多数堆栈缓冲区溢出都会被消除——这是一件大事。(因为字符串向上增长)。

(4004和8080架构师)解释了8080(最终是8086)的堆栈增长方向是如何选择的:

选择堆栈指针运行“下坡”(堆栈向较低内存前进),以简化从用户程序到堆栈的索引(正向索引),并简化从前面板显示堆栈内容


在最小嵌入式系统中递减堆栈增长的一个优点是,单个RAM块可以冗余地映射到第O页和第1页,允许从0x000开始分配零页变量,堆栈从0x1FF向下增长,从而最大限度地增加它在覆盖变量之前必须增长的量

6502最初的设计目标之一是,它可以与6530相结合,形成一个双芯片微控制器系统,在堆栈和零页变量之间共享1KB的程序ROM、定时器、I/O和64字节的RAM。相比之下,当时基于8080或6800的最小嵌入式系统将是四个或五个芯片

因为
POP
使用的寻址模式与通常用于扫描字符串和数组的寻址模式相同 从堆栈中弹出值的指令需要做两件事:从内存中读取值和调整堆栈指针。此操作有四种可能的设计选择:

  • 先预增量堆栈指针,然后读取值。这意味着堆栈将“向下”(朝向较低的内存地址)增长

  • Predecrement首先读取堆栈指针,然后读取值。这意味着堆栈将“向上”(向着更高的内存地址)增长

  • 首先读取该值,然后读取堆栈指针。这意味着堆栈将向下增长

  • 首先读取值,然后后减量堆栈p