为什么堆栈通常向下增长?

发布于 2024-08-17 12:03:42 字数 257 浏览 5 评论 0原文

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

我想知道这样做的历史依据。我知道,在统一的地址空间中,在数据段的另一端启动堆栈很方便(比如说),因此只有当两侧在中间发生冲突时才会出现问题。但为什么堆栈传统上会占据顶部呢?特别是考虑到这与“概念”模型如何相反?

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

I know that in the architectures I'm personally familiar with (x86, 6502, etc), the stack typically grows downwards (i.e. every item pushed onto the stack results in a decremented SP, not an incremented one).

I'm wondering about the historical rationale for this. I know that in a unified address space, it's convenient to start the stack on the opposite end of the data segment (say) so there's only a problem if the two sides collide in the middle. But why does the stack traditionally get the top part? Especially given how this is the opposite of the "conceptual" model?

(And note that in the 6502 architecture, the stack also grows downwards, even though it is bounded to a single 256-byte page, and this direction choice seems arbitrary.)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(11

ゝ偶尔ゞ 2024-08-24 12:03:42

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

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

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

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

As to the historic rationale, I can't say for certain (because I didn't design them). My thoughts on the matter are that early CPUs got their original program counter set to 0 and it was a natural desire to start the stack at the other end and grow downwards, since their code naturally grows upward.

As an aside, note that this setting of the program counter to 0 on reset is not the case for all early CPUs. For example, the Motorola 6809 would fetch the program counter from addresses 0xfffe/f so you could start running at an arbitrary location, depending on what was supplied at that address (usually, but by no means limited to, ROM).

One of the first things some historical systems would do would be to scan memory from the top until it found a location that would read back the same value written, so that it would know the actual RAM installed (e.g., a z80 with 64K address space didn't necessarily have 64K or RAM, in fact 64K would have been massive in my early days). Once it found the top actual address, it would set the stack pointer appropriately and could then start calling subroutines. This scanning would generally be done by the CPU running code in ROM as part of start-up.

With regard to the stacks growth, not all of them grow downwards, see this answer for details.

你如我软肋 2024-08-24 12:03:42

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

One good explanation I heard was that some machines in the past could only have unsigned offsets, so you'd want to the stack to grow downward so you could hit your locals without having to lose the extra instruction to fake a negative offset.

┊风居住的梦幻卍 2024-08-24 12:03:42

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

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

Stanley Mazor (4004 and 8080 architect) explains how stack growth direction was chosen for 8080 (and eventually for 8086) in "Intel Microprocessors: 8008 to 8086":

The stack pointer was chosen to run "downhill" (with the stack advancing toward lower memory) to simplify indexing into the stack from the user's program (positive indexing) and to simplify displaying the contents of the stack from a front panel.

别靠近我心 2024-08-24 12:03:42

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

One possible reason might be that it simplifies alignment. If you place a local variable on the stack which must be placed on a 4-byte boundary, you can simply subtract the size of the object from the stack pointer, and then zero out the two lower bits to get a properly aligned address. If the stack grows upwards, ensuring alignment becomes a bit trickier.

鸠魁 2024-08-24 12:03:42

IIRC 堆栈向下增长,因为堆向上增长。情况也可能相反。

IIRC the stack grows downwards because the heap grows upwards. It could have been the other way around.

月寒剑心 2024-08-24 12:03:42

因为 POP 使用通常用于扫描字符串和数组的相同寻址模式。

从堆栈中弹出值的指令需要执行两件事:从内存中读取值,以及调整堆栈指针。此操作有四种可能的设计选择:

  1. 预递增 (++*ptr) 首先堆栈指针,然后读取值。这意味着堆栈将“向下”增长(朝向较低的内存地址)。

  2. 首先预减 (--*ptr) 堆栈指针,然后读取值。这意味着堆栈将“向上”增长(朝向更高的内存地址)。

  3. 首先读取值,然后后递增 (*ptr++) 堆栈指针。这意味着堆栈将向下增长。

  4. 首先读取值,然后后递减 (*ptr--) 堆栈指针。这意味着堆栈将向上增长。


在许多计算机语言(特别是 C)中,字符串和数组作为指向其第一个元素的指针传递给函数。一个非常常见的操作是从第一个元素开始按顺序读取字符串或数组的元素。这样的操作只需要上述的后递增寻址模式。

此外,读取字符串或数组的元素比写入元素更常见。事实上,有许多标准库函数根本不执行任何写入操作(例如 strlen()strchr()strcmp())!


因此,如果指令集设计中的寻址模式数量有限,那么最有用的寻址模式将是读取后增量。这不仅会产生最有用的字符串和数组操作,还会产生一条使堆栈向下增长的 POP 指令。

第二个最有用的寻址模式是后递减写入,它可用于匹配的PUSH指令。

事实上,PDP-11 具有后递增和前递减寻址模式,这产生了向下增长的堆栈。甚至 VAX 也没有预增量或后减量。

Because then a POP uses the same addressing mode that is commonly used to scan through strings and arrays

An instruction that pops a value off of a stack needs to do two things: read the value out of memory, and adjust the stack pointer. There are four possible design choices for this operation:

  1. Preincrement (++*ptr) the stack pointer first, then read the value. This implies that the stack will grow "downwards" (towards lower memory addresses).

  2. Predecrement (--*ptr) the stack pointer first, then read the value. This implies that the stack will grow "upwards" (towards higher memory addresses).

  3. Read the value first, then postincrement (*ptr++) the stack pointer. This implies that the stack will grow downwards.

  4. Read the value first, then postdecrement (*ptr--) the stack pointer. This implies that the stack will grow upwards.


In many computer languages (particularly C), strings and arrays are passed to functions as pointers to their first element. A very common operation is to read the elements of the string or array in order, starting with the first element. Such an operation needs only the postincrement addressing mode described above.

Furthermore, reading the elements of a string or array is more common than writing the elements. Indeed, there are many standard library functions that perform no writing at all (e.g. strlen(), strchr(), strcmp())!


Therefore, if you have a limited number of addressing modes in your instruction set design, the most useful addressing mode would be a read that postincrements. This results in not only the most useful string and array operations, but also a POP instruction that grows the stack downward.

The second-most-useful addressing mode would then be a post-decrement write, which can be used for the matching PUSH instruction.

Indeed, the PDP-11 had postincrement and predecrement addressing modes, which produced a downward-growing stack. Even the VAX did not have preincrement or postdecrement.

冬天旳寂寞 2024-08-24 12:03:42

我相信这纯粹是一个设计决定。并非所有这些都向下生长 - 请参阅这个SO 线程 对不同架构上堆栈增长的方向进行了一些很好的讨论。

I believe it's purely a design decision. Not all of them grow downward -- see this SO thread for some good discussion on the direction of stack growth on different architectures.

葬シ愛 2024-08-24 12:03:42

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

I'm not certain but I did some programming for the VAX/VMS back in the days. I seem to remember one part of memory (the heap??) going up and the stack going down. When the two met, then you were out of memory.

苏佲洛 2024-08-24 12:03:42

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

I believe the convention began with the IBM 704 and its infamous "decrement register". Modern speech would call it an offset field of the instruction, but the point is they went down, not up.

凝望流年 2024-08-24 12:03:42

再多 2c:

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

我个人认为这是一个安全设计缺陷。比如说,如果 x64 架构的设计者能够反转堆栈增长方向,那么大多数堆栈缓冲区溢出就会被消除 - 这是一件大事。 (因为弦向上生长)。

Just 2c more:

Beyond all the historic rationale mentioned, I'm quite certain there's no reason which is valid in modern processors. All processors can take signed offsets, and maximizing the heap/stack distance is rather moot ever since we started dealing with multiple threads.

I personally consider this a security design flaw. If, say, the designers of the x64 architecture would have reversed the stack growth direction, most stack buffer overflows would have been eliminated - which is kind of a big deal. (since strings grow upward).

傲娇萝莉攻 2024-08-24 12:03:42

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

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

One advantage of descending stack growth in a minimal embedded system is that a single chunk of RAM can be redundantly mapped into both page O and page 1, allowing zero page variables to be assigned starting at 0x000 and the stack growing downwards from 0x1FF, maximizing the amount it would have to grow before overwriting variables.

One of the original design goals of the 6502 was that it could be combined with, for example, a 6530, resulting in a two-chip microcontroller system with 1 KB of program ROM, timer, I/O, and 64 bytes of RAM shared between stack and page zero variables. By comparison, the minimal embedded system of that time based on an 8080 or 6800 would be four or five chips.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文