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


95

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

我想知道这样做的历史依据。我知道在统一的地址空间中,在数据段的另一端(例如)启动堆栈很方便(因此,如果两侧在中间发生碰撞,则只有一个问题)。但是,为什么堆栈通常会占据顶部呢?特别是考虑到这与“概念”模型相反的情况?

(请注意,在6502架构中,即使堆栈绑定到单个256字节页面,堆栈也向下增长,并且这个方向选择似乎是任意的。)

Answers:


52

至于历史的基本原理,我不能确定地说(因为我没有设计它们)。我对此的想法是,早期的CPU会将其原始程序计数器设置为0,并且很自然地希望在另一端启动堆栈并向下扩展,因为它们的代码自然会向上扩展。

顺便说一句,请注意,并非所有早期的CPU 在复位时将程序计数器设置为0 。例如,Motorola 6809将从地址中读取程序计数器,0xfffe/f以便您可以在任意位置开始运行,具体取决于该地址提供的内容(通常但绝不限于ROM)。

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

关于堆栈的增长,并非所有堆栈都向下增长,有关详细信息,请参见此答案


1
我喜欢Z80 RAM检测策略的故事。从某种意义上说,文本段的布局是向上增长的-过去的程序员在处理这种含义方面比在堆栈上有更多的直接接触。感谢paxdiablo。指向堆栈实现的一组替代形式的指针也非常有趣。
Ben Zotto

早期记忆没有办法通知其大小,而我们必须手动计算吗?
phuclv

1
@LưuVĩnhPhúc,我必须假设您比我落后一代(或两代)。我仍然记得TRS-80型号3的获取日期和时间的方法是在引导时询问用户。拥有内存扫描器来设置内存上限被认为是当今最先进的技术:-)您能想象如果Windows每次启动时都询问时间或拥有多少内存会发生什么情况?
paxdiablo

1
实际上,Zilog Z80文档说,该部分通过将PC寄存器设置为0000h并执行来启动。它将中断模式设置为0,禁用中断,并将I和R寄存器也设置为0。之后,它开始执行。在0000h,它开始运行代码。该代码必须先初始化堆栈指针,然后才能调用子例程或允许中断。哪家供应商销售的Z80符合您的描述方式?
MikeB 2015年

1
迈克,对不起,我应该更清楚一些。当我说CPU扫描内存时,我并不是说那是CPU本身的功能。它实际上是由ROM中的程序控制的。我会澄清。
paxdiablo

21

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


7

Stanley Mazor(4004和8080架构师)解释了如何在“英特尔微处理器:8008至8086”中为8080(并最终为8086)选择了堆栈增长方向:

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


6

一种可能的原因可能是它简化了对齐。如果将局部变量放在必须放在4字节边界上的堆栈上,则可以简单地从堆栈指针中减去对象的大小,然后将两个低位清零以获得正确对齐的地址。如果堆栈向上生长,则确保对齐变得有些棘手。


1
电脑不减去;他们加了2的称赞。减法完成的任何事情实际上都是加法完成的。考虑一下,计算机有加法器,而不是减法器。
jww

1
@jww-这是没有区别的区别。我可能已经宣称计算机不会加,而只会减!出于此答案的目的,这并不重要-但是大多数ALU都将使用支持相同性能的加法和减法的电路。就是说,虽然A - B可以在概念上实现为A + (-B)(即的一个单独的否定步骤B),但实际上并没有实现。
BeeOnRope

@jww对于早期的计算机,您的nitpick是错误的-赢得补码花了一些时间,直到做到这一点,有些计算机才使用补码和正负号,也许还有其他东西。通过这些实现,加法与减法很可能具有优势。因此,在没有其他信息的情况下,将其排除为可能影响寻址方案选择(如堆栈方向)的因素是错误的。
mtraceur

4

IIRC堆栈向下增长,因为堆栈向上增长。可能是另一回事。


5
在某些情况下,向上增长的堆允许有效的重新分配,但是向下增长的堆几乎从来没有这样做。
彼得·科德斯

@PeterCordes为什么?
Yashas,

3
@Yashas:因为对象之后realloc(3)需要更多空间以仅扩展映射而不进行复制。当后面有任意数量的未使用空间时,可能会重复重新分配同一对象。
彼得·科德斯


1

我相信惯例始于IBM 704及其臭名昭著的“减量寄存器”。现代语言将其称为指令的偏移量字段,但要点是它们下降了而不是 上升了


1

再加2分:

除了提到的所有历史依据外,我敢肯定,没有理由在现代处理器中是有效的。所有处理器都可以采用带符号的偏移量,并且自从我们开始处理多个线程以来,最大程度地提高堆/堆栈的距离就显得毫无意义。

我个人认为这是安全设计缺陷。如果说x64体系结构的设计者会改变堆栈的增长方向,那么大多数堆栈缓冲区溢出将被消除-这是很大的事情。(因为琴弦向上生长)。



0

在最小的嵌入式系统中,堆栈增长下降的一个优点是可以将单个RAM块冗余地映射到页面O和页面1中,从而允许从0x000开始分配零页面变量,并且堆栈从0x1FF开始向下增长,从而最大化了覆盖变量之前必须增加的数量。

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.