【问题标题】:How does a stack stores values in it?堆栈如何在其中存储值?
【发布时间】:2020-11-17 10:23:55
【问题描述】:

我正在观看视频What is stack and why do we need it?,这是讲师向我们展示如何使用 Javascript 制作 16 位 VM 的课程的一部分。

我的问题来自视频的一帧中堆栈的表示。

让我快速向大家介绍这个框架中发生的事情。在图像的左侧,我们有所有的寄存器:

  • ip:指令指针
  • acc:累加器
  • r1-r4:通用寄存器
  • sp: Stack Pointer

在图像的右上角,我们有接下来要执行的指令。下面是堆栈的当前状态。

现在,如图所示,sp 当前指向地址$fffa,对应的值为$0000。在此之前,sp 指向地址$fffc,要执行的指令是psh r2。同样,在此之前,sp 指向地址$fffe,指令为psh r1

现在,在整个场景中我不明白的是地址会随着我们向下移动而增加,例如上面的地址是$ffe6,下面的地址是$ffe8,明显比它的前身要大,而且地址还在不断增加。但是为什么我们的堆栈指针sp首先以地址$fffe开头呢?我知道堆栈以 FILO(先进后出)/LIFO(后进先出) 方式工作,如果我们只是忽略地址的顺序,我们将遵循 FILO/LIFO。但令我困惑的是地址的顺序。如果我们将堆栈逆时针旋转 90 度,那么我们的堆栈将看起来像这样。

为什么我旋转堆栈是为了了解内存地址在任何架构中的精确布局。

现在,我有这些问题。

  1. 为什么堆栈指针从堆栈中的最后一个地址开始?
  2. 这真的是在所有语言中实现堆栈的方式吗?
  3. 这种实现堆栈的方式是否有助于避免因堆栈溢出而出现的问题?
  4. 和栈和堆在内存中的存储方式有关系吗?
  5. 如果我们从地址 $ffe6 开始会发生什么变化?

我可能在一篇文章中问了很多问题,但请把你的大部分注意力放在最后一个问题上,因为我认为它可能会回答所有其他问题。谢谢。

【问题讨论】:

  • 希望你能从这里得到一些澄清:(stackoverflow.com/questions/4560720/…) (stackoverflow.com/questions/2744502/…) (stackoverflow.com/questions/2035568/…)。我建议你回答一个简单的问题“为什么堆栈地址会向下增长”。
  • 堆栈向下增长,以便堆栈指针可以设置为最高内存地址,它会在覆盖程序数据之前自动使用所有可用内存。地址$fffe 是可寻址的最高 16 位字,您可以使用任何您想要的值。不同之处在于您获得的堆栈空间更少。从上到下按升序排列地址也一直困扰着我,我更喜欢在顶部绘制更高的地址。
  • 当然,并非所有架构都使用向下增长的堆栈,并且堆栈并不总是位于地址空间的顶部或底部。

标签: javascript assembly stack cpu-architecture callstack


【解决方案1】:

这个问题很危险地接近过于宽泛和主要基于意见的界限,但我明白你在问什么。

了解历史上存在无数不同的处理器设计和系统实现。语言和处理器随着时间的推移而发展。因此,任何绝对声明实际上都是有限的,因为毫无疑问,该声明不适用于某个系统或处理器。

通常堆栈只是内存,堆栈指针只是该内存中的地址/偏移量,push/pop 与普通内存访问有何不同是程序员通常不/不应该关心的具体情况地址,而是相对的,我推了五件事,所以第三件事离堆栈指针很远,要清理我需要弹出 5 件事,等等。但它只是一个带有地址指针的地方的 ram。

虽然我们认为编号越低的地址越低,编号越大的地址越高,并且期望内存的绘图/可视化具有较低的编号地址在图表上较低且较高的地址在图表上,但有时是有充分理由的或者有时不是这样。在芯片上并没有真正的上升或下降,也没有假设内存是以某种长的物理线性 2D 方式布局的,这些都是简单的可视化。

我不知道有什么异常,但通常处理器以地址递增的方向执行,地址 0x1000 处的一条指令长度为 4 个字节,下一条指令假定位于 0x1004,而不是 0xFFC。所以让我们假设代码向上或从低地址向高地址增长。

假设我们的固件在 ram 中运行,而不是在 flash 中,我们正在谈论 ram 的消耗。并且考虑裸机而不是同时加载许多应用程序的操作系统。

一个程序通常会有一些代码(通常称为 .text)、一些数据、(全局)变量等(通常称为 .data 和 .bss)。堆是运行时分配的内存和堆栈。

我没有对此进行研究,但根据我所教的内容和名称本身,人们可以将 stack 视为一堆盘子或一堆便条卡。由于重力向上生长。并且独立于处理器架构,将堆栈可视化为向上增长的情况并不少见,新项目放置在旧项目之上,移除顶部项目以获得较低项目。但这不是那么严格,不确定它是否是 50/50,但您会经常看到它被可视化为向下和向上增长。或者一个滑动窗口,堆栈指针在图表中没有视觉移动,但数据根据显示方式上下移动。

另外请注意,该站点的名称 Stack Overflow,该术语对它的含义有一个特定的假设......

所以切入正题,经典模型(后面会提到例外情况)是从较低的内存开始,或者我们甚至假设为零,你有你的代码、机器代码以及属于该类别的任何其他内容。然后你有你的全局变量 .data 和 .bss,然后你有你的堆,最上面的是你的堆栈。堆和堆栈在运行时被认为是动态的。如果您从未释放,则假定堆向上增长。所以堆栈的自然解决方案是让它向下增长。你从最低地址开始你的堆,理想情况下你可以在其他项目(.text,.data,.bss)的顶部和尽可能高的堆栈,以便堆栈溢出(堆栈和堆冲突,堆栈增长到堆分配的内存中)。

这种传统模型意味着堆栈向下增长意味着从较高地址到较低地址。许多指令集架构将推送/弹出解决方案限制在此范围内,使用所设计的指令堆栈向下增长存在例外情况,例如传统(aarch64 之前)arm 指令(全尺寸而非拇指)可以采用任何一种方式,因此情况是编译器作者的选择,而不是由架构强制。可以说,使用可以访问内存的通用寄存器,编译器可以选择使用简单的加载/存储指令,而不是推送/弹出或等效指令,然后做任何他们想做的事情。但除了可能非常有限的例外,从地址角度来看,堆栈会向下增长。

一些架构的堆栈被埋在不可见的空间中,旧的旧芯片相对于今天可能有一个非常小的堆栈,比如 16 深或 32 深,我们唯一的访问是推送和弹出,仅此而已。

一些具有 push/pop 或等效功能的架构,例如在 push 时会写入然后调整堆栈指针或调整堆栈指针然后写入,因此对于 16 位系统,您可以从 0x10000 开始获取所有位置您不能代表 0x0000,其他 0xffff 或 0xfffc 取决于架构及其工作方式等。

因此,如果您想将堆栈可视化为实际上是一堆东西,一堆便笺卡,一堆盘子等。然后由于重力,您会将其可视化为向上生长。我在便条卡上写了一个数字,将其放在堆栈上 在便条卡上写下另一个数字,然后将其放置(推)到堆栈上,取出卡(弹出)等等。因此,由于它是一个 50/50 的东西,您有时会看到堆栈以这种方式可视化,较高的地址位于图表的下部,较低的地址位于图表的上部。

因此基于意见,这就是他们以这种方式绘制图表的原因。归根结底,要做好心理准备,以应对人们想象堆栈的任何方式。

  1. 为什么堆栈指针从堆栈中的最后一个地址开始?

这是经典意义上的典型。但是,在现实世界中,有些用例将堆栈放置在与其他项目不同的内存空间中,这些项目可能会受到安全功能(mmu 等)的保护而不会超出其空间。但是,堆栈指针和/或指令的正常使用是堆栈相对于所使用的内存地址向下增长,这通常是一个架构限制。所以如果你长大了,你想从高处开始。最后一个地址是一种教科书式的方法,但您经常会看到人们在链接描述文件中分配堆栈空间,然后它就在它所在的位置(有时甚至在堆或数据之下)。

  1. 这真的是在所有语言中实现堆栈的方式吗?

太宽泛了,语言本身编译成的代码使用指令、链接和引导程序(或操作系统)来确定程序堆栈的初始值。基于堆栈指针的指令被限制为向下增长的堆栈并不少见。如果有基于意见的选择,我预计由于历史原因,实施将向下(地址)增长。

  1. 这种实现堆栈的方式是否有助于避免因堆栈溢出而出现的问题?

是的,如果我们假设堆向上增长而堆栈向下增长,那么您希望堆从可用空间的底部开始,而顶部的堆栈在堆栈溢出发生之前提供最大的空间。

  1. 和栈和堆在内存中的存储方式有关系吗?

是的,基于意见。如上所述。

  1. 如果我们从地址 $ffe6 开始会发生什么变化?

实际上,每个“函数”都被称为堆栈指针所在的位置,这就是您不关心地址的全部意义,只关心匹配推送和弹出或可能的相对寻址,而不是绝对的。因此,如果 $ffe6 那么当您推送和弹出地址时,地址会变小/变大。如果 8000 美元,同样的交易 5432 美元,同样的交易。如果您从与教程中显示的地址不同的地址开始,一切都一样,只是显示的物理地址需要反映新的起点。


所以是的,堆栈的传统/教科书视图是后进先出的。地址空间向下增长,但 50/50 的文本作者如何在图表底部或顶部以高地址可视化这一点。实际上,更高性能的指令集不仅限于严格的推送和弹出,还包括相对寻址,所以当你开始学习推送/弹出时,你就可以直接进入相对寻址。我将 5 个东西压入堆栈,我可以使用 sp+offset 寻址访问所有这些东西,有时使用基于特殊 sp 的指令。

不要为某些教程/教科书作者如何可视化堆栈、顶部或底部的更高地址而烦恼。

【讨论】:

    猜你喜欢
    • 2016-03-05
    • 2015-03-15
    • 2013-12-06
    • 2013-12-04
    • 2015-03-09
    • 2013-08-24
    • 1970-01-01
    • 2021-01-13
    • 1970-01-01
    相关资源
    最近更新 更多