【问题标题】:When does the stack really overflow?堆栈何时真正溢出?
【发布时间】:2009-12-07 05:50:27
【问题描述】:

无限递归是唯一的情况还是会因为其他原因而发生? 栈大小不是和堆一样按需增长吗?

很抱歉,如果以前有人问过这个问题,如果是这样的话,将不胜感激他们的链接。

【问题讨论】:

    标签: stack


    【解决方案1】:

    我不能代表所有平台,但碰巧的是,我刚刚花了一些时间处理 Windows .exe 文件(我的意思是,实际上研究它们的二进制格式 - 从某种意义上说,我知道我们所有人这里使用可执行文件;))。我敢打赌,大多数其他平台都具有类似的功能,但我对它们并不熟悉。

    部分文件格式本身包含两个与当前讨论相关的值:

    typedef struct _IMAGE_OPTIONAL_HEADER {
        ...
        DWORD   SizeOfStackReserve;
        DWORD   SizeOfStackCommit;
        ...
    } IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;
    

    来自 MSDN:

    SizeOfStackReserve

    要保留的字节数 堆。只有指定的内存 SizeOfStackCommit 成员是 在加载时提交;剩下的就是 一次提供一页 直到达到这个保留大小。

    SizeOfStackCommit

    提交的字节数 堆栈。

    换句话说,链接器指定程序堆栈的最大大小。如果你达到最大尺寸,你就会溢出——不管你如何达到最大尺寸。您可以编写一个简单的程序来在一行代码中执行此操作,只需分配一个大于最大堆栈大小的堆栈变量(例如,一个数组)。或者您可以通过无限(或有限,但非常深)递归来实现,或者只是分配太多堆栈变量。

    Microsoft 链接器在 X86 平台上默认将此值设置为 1MB(在 Itanium 系统上为 4MB)。对于现代系统而言,这从表面上看似乎很小。但是,更现代的 Windows 版本对这些值的解释略有不同。它不是完全限制堆栈,而是限制堆栈将使用的物理内存。如果您的堆栈超出此范围,则会涉及虚拟内存,因此您应该仍然很好......假设您有足够的虚拟内存。

    请记住,即使在具有大量 RAM 和大量磁盘虚拟内存的现代系统上,也可能会耗尽内存。你只需要分配大量的数据。

    所以,长话短说:有可能在没有无限递归的情况下溢出堆栈吗?确实。有可能吗?不是真的,除非你分配了非常大的对象。

    【讨论】:

    • 很好的解释。谢谢。这是如何分配堆栈的一般概念,还是不同的操作系统可以不同地工作?尤其是那些基于 UNIX 的?
    • 不要引用我的话,但我相信在基于 Linux 的系统上,堆栈大小设置为系统范围的设置,而不是每个可执行的值。但是,除此之外,该过程通常是相似的。
    • 在基于 linux 的系统上,ulimit -a 告诉您默认线程堆栈大小 (8MB),它可以根据您的要求进行调整。
    • 除了巨大的内存分配或无限递归之外,还有很多情况会导致堆栈耗尽。智能手机、平板电脑和其他这类东西的内存可能有限。此外,堆栈是上下文相关的:例如,在 32 位 Windows 上的 x86 内核模式下,它只有 12kb。
    【解决方案2】:

    当堆栈指针被推出操作系统为堆栈分配的内存块时,堆栈溢出。一些操作系统会随着堆栈的增长而调整大小(IIRC Linux 会这样做),而在其他操作系统中,堆栈大小会在进程或线程开始时固定(IIRC Windows 会这样做)。

    堆栈溢出的可能原因:

    • 无限数量的堆栈帧(例如来自无限递归)
    • 尝试从堆栈分配大块
    • 堆栈上分配的缓冲区的缓冲区溢出

    可能还有其他我无法想到的原因。

    【讨论】:

      【解决方案3】:

      这个问题没有指定哪个堆栈是“那个”堆栈。所以,这里有几个答案:

      调用堆栈

      每当堆栈上的调用次数超出其拥有的内存量时,调用堆栈就会溢出。最常见的方法是无限递归,但很有可能递归过度但不是无限的。例如,天真地计算the Ackermann function 会对任何计算机征税。

      语言

      基于堆栈的语言

      一些语言,如 Postscript 和 Forth,以及一些虚拟机,如 Java 虚拟机,是基于堆栈的。在这些语言中,可能会使表达式变得如此复杂以至于溢出堆栈。

      上下文无关语言

      上下文无关语言通常使用堆栈来实现。如果这些语言的代码字符串过于复杂,则可能会溢出堆栈。

      【讨论】:

        【解决方案4】:

        在笔记本电脑或台式机上,从主线程运行时,如果没有无限(或非常深的嵌套)递归,堆栈溢出可能是不寻常的......但是,堆栈溢出并不少见:

        • 线程被分配了一个小的、固定大小的堆栈的线程代码。
        • 信号处理代码,其中信号处理上下文有一个小的、固定大小的堆栈。
        • 在内存通常稀缺的嵌入式设备上执行的代码。

        例如,如果您使用sigaction 注册信号处理程序,如果信号处理程序执行任何复杂的操作(即深度嵌套操作),在许多操作系统上很容易发生堆栈溢出,因为信号处理程序通常分配一个小的、固定大小的堆栈。同样,如果您使用pthread_create 生成一个线程,但您使用pthread_attr_setstacksize 指定一个小的堆栈大小,那么很容易实现堆栈溢出。在无线传感器等内存非常有限的设备上,避免堆栈溢出是一门艺术。

        【讨论】:

          【解决方案5】:

          我的日常工作涉及大量使用 Lotus Notes 中的 LotusScript,它对各种范围都有固定的堆栈限制。例如。过程/函数中的大多数变量必须适合 32kB 堆栈,除了类变量的内容存储在堆上。
          如果固定大小的变量超过堆栈大小,代码将无法编译。
          运行时堆栈溢出可能发生在递归中。这在 LotusScript 中很容易实现,因为它将任何单个函数的递归限制为 32kB 堆栈。几年前,我因此放弃了使用递归快速排序。

          【讨论】:

            【解决方案6】:

            如果您的程序超出了分配的堆栈空间而没有进行任何无限递归,那么您做错了什么。

            虽然如果你去掉一些星号并尝试按值传递一些巨大的缓冲区,它可能会发生。

            为堆栈分配的内存通常会在合理的范围内按需增长 - 我不确定各种系统的上限是多少。

            【讨论】:

            • 为堆栈分配的内存不会按需增长。
            • 我很抱歉,你是对的。线程堆栈的地址空间是预先分配的,页面实际上是按需提交的。
            • 仅在 Windows 上。 Linux 会根据需要扩展堆栈。
            猜你喜欢
            • 2019-05-18
            • 2013-10-30
            • 2021-05-28
            • 2011-05-16
            • 2012-04-15
            • 2012-12-20
            • 2011-03-02
            • 2011-08-22
            相关资源
            最近更新 更多