为了账号安全,请及时绑定邮箱和手机立即绑定

堆栈如何在其中存储值?

堆栈如何在其中存储值?

慕雪6442864 2022-12-09 16:26:04
我正在观看视频什么是堆栈以及我们为什么需要它?这是课程的一部分,讲师向我们展示了如何使用 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 度,那么我们的堆栈将看起来像这样。我旋转堆栈的原因是为了了解内存地址在任何体系结构中的布局方式。现在,我有这些问题。为什么堆栈指针从堆栈中的最后一个地址开始?这真的是所有语言中堆栈的实现方式吗?这种实现堆栈的方式是否有助于避免由于堆栈溢出而出现的问题?跟栈和堆在内存中的存储方式有关系吗?如果我们从地址开始,会有什么变化$ffe6?我可能在一个帖子中问了很多问题,但请最注意最后一个问题,因为我认为它可能会回答所有其他问题。谢谢你。
查看完整描述

1 回答

?
喵喔喔

TA贡献1735条经验 获得超5个赞

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

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

一般来说,堆栈只是内存,堆栈指针只是该内存中的地址/偏移量,push/pop 与普通内存访问的不同之处在于程序员通常不/不应该关心特定地址,但是相反,我推了五个东西,所以第三个东西离堆栈指针这么远,为了清理我需要弹出 5 个东西,等等。但它只是一个带有地址指针的 ram。

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

我不知道有没有例外,但通常处理器会按地址递增的方向执行,地址为 0x1000 的指令长度为 4 个字节,下一条指令假定为 0x1004,而不是 0xFFC。因此,让我们假设代码向上增长或从低地址增长到高地址。

假设我们的固件在 ram 而不是闪存中运行,我们正在谈论 ram 的消耗。并从裸机的角度思考,而不是一次加载许多应用程序的操作系统。

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

我没有对此进行研究,但根据我所学的内容和名称本身,可以将堆栈视为一叠盘子或一叠记事卡。由于重力而向上生长。独立于处理器架构,将堆栈可视化为向上增长并不少见,新项目放置在旧项目之上,移除顶部项目以获取较低项目。但这并不是那么死板,不确定它是否是 50/50,但你会经常看到它可视化为向下和向上增长。或带有堆栈指针的滑动窗口不会在图表中视觉移动,但数据会根据显示方式向上或向下移动。

另请注意,该站点的名称 Stack Overflow,该术语具有特定的含义假设......

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

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

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

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

所以如果你想把一堆想象成一堆东西,一堆便条卡,一堆盘子,等等。那么由于重力,你会把它想象成向上生长。我在记事卡上写一个数字,把它放在堆栈上,在记事卡上写另一个数字,然后把它放在(推)堆栈上,取出卡片(弹出)等等。因此,由于它是 50/50 的比例,您有时会看到以这种方式可视化的堆栈,其中较高的地址位于图表的下部,而较低的地址位于图表的上部。

所以基于意见,这就是为什么他们以这种方式绘制图表的原因。在一天结束时,请做好心理准备,以处理人们想象堆栈的任何方式。

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

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

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

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

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

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

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

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

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

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


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

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


查看完整回答
反对 回复 2022-12-09
  • 1 回答
  • 0 关注
  • 87 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信