有多少种不同的东西叫做#34; stack"在那里,他们如何相互联系?如果有人说"堆栈"我可以合理地假设它们是指其中之一,还是我只能从上下文中找出它?
但首先,我想概述一下我的理解是什么。
在编程中似乎有很多含糊不清的相关内容,它们都被称为"堆栈","堆栈"或类似的东西。除了数据结构之外,我甚至不确定这个术语是指同一个东西还是不同的东西。
基于this page,似乎"堆栈"甚至不是堆栈数据结构。虽然你只把东西放在它上面,你可以访问堆栈中的任何元素而不必弹出它之前的所有元素。除非我完全误解了数据结构。在我读到之前,使用堆栈作为变量数据结构的想法似乎是一个非常糟糕的决定。
然后出现"堆栈",调用堆栈和硬件堆栈的问题。我认为硬件堆栈是调用堆栈,因为你可以在其中放入指令(?),但它也可能不是你存储固定大小变量的地方。或者是吗?为什么堆栈甚至必须在硬件中实现?我认为变量存储在RAM而不是处理器的内存中。或者是特定的调用堆栈语言,因此不是硬件?
然后是堆栈帧,除了数据结构之外,它似乎对所有堆栈都是一般的。
我无法弄清楚如何正确地进行谷歌搜索,因为我不确定人们是否在谈论同样的事情。我试图理解的越多,我就越困惑
答案 0 :(得分:3)
<强> TL; DR 强>
当我们允许程序调用子程序时,我们需要跟踪调用堆栈(如下所述)。这可以通过各种方式实现,仅受您的创造力的限制,但最简单和最快捷的方式是每次推/弹时都会增加或减少的指针。由于这很容易实现和使用,因此CPU可以提供删除样板代码的指令和寄存器(并且可以进行优化)。那是硬件堆栈。
堆栈是支持 LIFO 范例的任何结构。
CPU没有内在需要为堆栈提供支持(例如,MIPS不要这样做),但无论如何最终都会使用它。
要理解原因,只需考虑调用子程序的行为。
调用子程序时,我们需要记住它的返回地址
即使CPU将地址存储在寄存器中,比如 $ ra ,问题是被调用者可能需要调用另一个子程序!
所以我们需要先保存 $ ra ,然后再拨打新电话
同样,这个新的被叫者可能需要再次拨打电话,问题重复,可能无限次。
如果你仔细想想,你会意识到:
这是 LIFO 行为,我们需要一个堆栈,因此名称为调用堆栈。
如何实际实现堆栈是体系结构定义的,如果CPU是通用的,则不太可能使用某种形式的内部,不可访问的内存。我们已经可以访问RAM,为什么不使用它呢?这样我们就递归的深度没有内部限制。
在组装过程中,一切都由它的使用组成 我们不需要复杂的元数据来实现堆栈,只需要一个简单的指针 它取决于程序员确保它永远不会超过推动时间或确保新推送的元素不会覆盖某些东西。
大多数汇编程序员最终使用寄存器来跟踪堆栈的头部 由于这是一种普遍实现的技术,因此CISC CPU具有用于管理堆栈的专用寄存器和指令。
如果您想使用该名称,这是硬件堆栈。
您没有那么自由地选择实施,因为您的程序通常不会在裸机上独立运行,您需要坚持使用ABI来指示堆栈如何实施。
汇编编程就像量子物理一样,是程序员(观察者)决定事物的本质。
由于程序可以自由访问其RAM,因此无需使用专用指令即可重新实现上述所有堆栈操作。
如果程序员可以访问堆栈指针(它应该是或者我们不能在第一时间设置堆栈),我们可以将它用作基本指针并欺骗堆栈结构访问元素而不是在上面。
这是普通的装配编程。一切都是通过使用而非固定规则来定义的。
注意我们不能删除元素不在顶部,因为唯一定义堆栈的是它的头指针,堆栈的每个属性都由该指针定义。
包括它的大小。
一个统一的约定是使用堆栈来存储子程序参数(如果我们已经填充了所有寄存器)和局部变量。
这是由于堆栈的一个非常有趣的属性:在堆栈上分配/释放内存非常容易 只需来回移动指针即可 假设堆栈指针指向一个足够大的存储区域以用于通用目的,您可以将其视为已分配的内存块并且堆栈指针后面的所有内容都在使用中(同时一切都可以突然被覆盖)。
如果你重新考虑一下调用堆栈,你会发现这是做这些事情的自然方式。
当调用一个新的子例程时,通过改变堆栈指针来分配堆栈上的新空间,当它返回时,释放该空间,恢复堆栈指针。
调用者将始终将其数据放在与堆栈指针相同的相对位置。
此外,每个例程都会在堆栈的最后看到它的数据(和参数),与之前的调用次数无关。