堆栈内存是连续的吗?

Posted

技术标签:

【中文标题】堆栈内存是连续的吗?【英文标题】:Is stack memory contiguous? 【发布时间】:2011-07-02 11:25:51 【问题描述】:

编译器如何强制堆栈内存是连续的,它是否会导致程序在每次运行时移动内存,或者它是否在程序运行之前将内存保留在程序所需的堆栈上?

【问题讨论】:

哪个操作系统? C++ 没有“堆栈内存”。 视窗。但是没有栈内存怎么办? @user:一切都可以动态分配。 我们在谈论“堆内存”吗? 为什么堆栈应该是连续的。对于这种令人窒息的限制,语言操作系统或硬件没有要求。 【参考方案1】:

没有要求堆栈在操作系统或硬件语言中是连续的。

我向任何人提出挑战,要求提供明确说明这是一项要求的参考。

现在很多实现都使用连续内存,因为它很简单。这也是向 CS 学生教授堆栈概念的方式(堆栈向下增长,堆向上扩展)。但是没有要求这样做。我相信 MS 甚至尝试将堆栈帧放置在堆中的随机位置,以防止使用故意的堆栈粉碎技术进行攻击。

堆栈的唯一要求是帧是链接的。因此允许堆栈在进入/离开范围时推送/弹出帧。

但这一切都与原始问题正交。

编译器不会尝试强制堆栈位于连续内存中。语言级别没有要求堆栈是连续的。

堆栈通常是如何实现的。

如果这是问题。然后你会从社区中得到更详细、更准确的答案。

【讨论】:

I challenge anybody to site a reference that explicitly says this is a requirement.... 好吧,我挑战你提供一个例子来说明它是如何工作的,例如,在 Windows x86 系统上。 :) @Mehrdad:如果你仔细阅读,我已经解释了如何。每个堆栈帧只需要一个指向前一个堆栈帧的指针。弹出堆栈帧是用前一个堆栈帧的指针重置堆栈指针的问题(它保存在当前堆栈帧中。与堆栈帧在连续空间中的位置完全相同)。堆栈在连续空间中或通过堆分布时,实现没有变化。一些 CPU 有特殊的指令来推送/弹出堆栈帧并在一条指令中移动 SP,因此需要更多的工作。 @Mehrdad:请仔细阅读。没有需要连续堆栈的操作系统限制。尽管操作系统供应商实施可能出于多种原因(包括但不限于效率)将其实施限制为连续堆栈。 @Mehrdad:could you replace your sentences with "Windows"。不,我试图非常具体地说明我所说的话,不管你是否扭曲了这个话题。你又错过了重点。操作系统的供应商特定实现可以对其使用施加任意限制(例如 Windows 可能对堆栈施加连续的内存要求(我想))。但不存在需要连续堆栈的操作系统限制。 @Mehrdad:也许不是你想要的。但是我从来没有提到一个特定的操作系统,这是有充分理由的。我说有机体不需要呼吸空气。你说给我看一个不需要呼吸空气的人。 :-) 争论这一点毫无意义。【参考方案2】:

给定线程的堆栈通常在虚拟内存中是连续的(在 Linux 和类似系统上,以及在 Windows 的用户模式下)。 Windows kernel (in Windows Vista and above) 和 z/OS 允许虚拟内存中的不连续堆栈,GCC 4.6 will also allow that。编译器根本不需要移动堆栈,即使对于堆栈的虚拟地址不连续的系统也是如此;他们只是改变新零件的分配位置。操作系统可能会将物理页面重新映射到虚拟页面,以便堆栈在物理内存中可能不连续,即使它在虚拟内存中也是如此。

【讨论】:

@Jeremiah:“几乎总是”很有趣……知道其他例子吗? @Merhdad:如果你非常小心的话,我相信有一个技巧可以移动堆栈的各个部分。 GCC 4.6 号称可以做到:gcc.gnu.org/wiki/SplitStacks. @Mehrdad:他们正在谈论每个线程的堆栈是不连续的(可能)以允许动态调整其大小。 @Mehrdad:请参阅 msdn.microsoft.com/en-us/library/89f73td2%28v=vs.80%29.aspx 以获取示例程序。 @Mehrdad:看download.microsoft.com/download/9/c/5/…的第7页【参考方案3】:

你有你的内存地址空间,假设它从 1 运行到 100。你从 1 向上分配你的堆栈,你从 100 向下分配你的堆。到目前为止还好吗?

由于堆栈的特性,它总是很紧凑(没有孔)。发生这种情况是因为堆栈中的所有内容都是被调用的某个函数的上下文。每当一个函数退出时,它的上下文就会从栈顶移除,然后我们回退到前一个函数。我认为如果你有一个调试器,并且只遵循函数调用,同时记住堆栈必须是什么,你就可以很好地理解它。

另一方面,堆的表现不太好,假设我们为堆保留了 70 到 100 的内存。我们可以在那里分配一个 4 字节的块,它可能从 70 到 74,然后我们再分配 4 个字节,现在我们分配了从 70 到 78 的内存。但是该内存可能在程序的任何时候被释放。因此,您可能会释放一开始分配的 4 个字节,从而创建一个空洞。

这就是在您的地址空间中发生的事情。内核保留了一张表,将地址空间中的页面映射到实际内存中的页面。您可能已经注意到,当您运行多个程序时,您不能希望所有的一切都设置得那么好。所以内核所做的就是让每个进程认为整个地址空间是连续的内存(我们暂时不要考虑内存映射设备),即使它可能在内存中被非连续映射。

我希望对这个主题给出一个合理的概述,但可能有比我更好的作者,你可能会更喜欢阅读。因此,在虚拟内存中查找文本,这可能是您了解所需内容的一个很好的起点。有几本书或多或少地详细描述了它。我知道的一些:结构化计算机组织,tanenbaum;操作系统概念,作者 Silberschatz。我很确定 Knuth 在他的算法书籍中也讨论过它。如果您喜欢冒险,可以尝试阅读英特尔手册上的 x86 实现。

【讨论】:

以上是关于堆栈内存是连续的吗?的主要内容,如果未能解决你的问题,请参考以下文章

C之堆栈

一个类的内存布局是连续的吗?

iOS堆栈内存区别

堆栈与ESP(栈指针寄存器)

动态内存使用速度较慢是啥? [复制]

java 堆栈内存分析详解