【问题标题】:Stack overflow error or exception?堆栈溢出错误或异常?
【发布时间】:2011-03-30 23:29:47
【问题描述】:

为什么下面的结果没有错误?

void func()
{
   func();
}

int main()
{
   func();
}

【问题讨论】:

  • 应用程序是否只是挂起,还是在命令提示符处以返回结束且没有备注?您得到的答案的正确性可能取决于您对这个问题的回答。
  • 输出显示:Press any key to continue . . .
  • 所以这是某种崩溃,而不是持续循环消耗 CPU?
  • 按下一个键会发生什么?
  • @vbence 退出并关闭控制台窗口。

标签: c++ recursion


【解决方案1】:

理论上,它会溢出堆栈(因为,即使没有使用局部变量,每次调用都会在堆栈上添加之前的返回地址); 在实践中,启用优化后,它不会因为tail call optimization 而溢出,这实际上避免了在跳转中转换调用的任何资源消耗,因此不会消耗堆栈。

这可以通过 OP 代码生成的examining the optimized assembly 很容易看出:

func():
.L2:
        jmp     .L2
main:
.L4:
        jmp     .L4

func 优化为无限循环,“独立版本”和main 中的内联调用。

请注意,这与 C++ 标准的“好像”规则是一致的:编译后的程序必须运行就像它是您在代码中所要求的(就效果而言),并且由于堆栈大小只是一个实现限制,因此使用call 生成的代码和使用jmp 的代码是等效的。

但是:这是一个更特殊的情况,因为标准甚至说无限循环(定义为"not terminating and not having some side-effect")实际上是未定义的行为,所以理论上编译器将被允许完全省略该调用。

【讨论】:

  • +1 到这个答案,我想说的更明确的版本。
【解决方案2】:

很可能,您的编译器对其进行了优化并将其转换为 while(true){} 构造。

【讨论】:

  • @GMan:我猜这都是措辞。
  • @David:是的,有时我只是错过了目标并做出了太多假设,哦,好吧。
【解决方案3】:

确实在我的 Linux 系统上以 分段错误结束 - Valgrind 表示可能的堆栈溢出,这当然是真的,因为对于每个函数调用一个新堆栈框架是必需的。

然而,在编译器中启用优化会将整个程序减少为一个无限循环,当然,它根本不会结束:

        .file   "so.c"
        .text
        .p2align 4,,15
.globl func
        .type   func, @function
func:
.LFB0:
        .cfi_startproc
        .p2align 4,,10
        .p2align 3
.L2:
        jmp     .L2
        .cfi_endproc
.LFE0:
        .size   func, .-func
        .p2align 4,,15
.globl main
        .type   main, @function
main:
.LFB1:
        .cfi_startproc
        .p2align 4,,10
        .p2align 3
.L5:
        jmp     .L5
        .cfi_endproc
.LFE1:
        .size   main, .-main
        .ident  "GCC: (GNU) 4.4.3"
        .section        .note.GNU-stack,"",@progbits

这是有趣的部分:

.L5:
        jmp     .L5

【讨论】:

    【解决方案4】:

    如果您在 Windows 上的命令窗口中编译和运行它,您可能会遇到崩溃,但操作系统没有任何说明。 (我们构建了一个有趣的编译器并经常遇到这个问题)。微软的说法是,当程序做了非常糟糕的事情时,他们无法恢复......所以他们只是简单地杀死进程并重新启动命令提示符。可能在您的情况下,在您递归到堆栈限制后,当陷阱处理程序尝试执行某些操作(例如在堆栈上推送陷阱状态)时,没有任何空间并且 Windows 会终止您的进程。

    我个人认为这是不可原谅的行为。如果我的进程做错了什么,操作系统应该总是抱怨。它可能会说,“进程因偏见而终止”,以及某种指示(“你在最后的错误处理程序中用完了堆栈”),但它应该说些什么。

    Multics 在 1966 年做到了这一点。遗憾的是,我们已经 40 多年没有应用这些经验了。

    【讨论】:

      【解决方案5】:

      在我的机器上,它以段错误结束(就像无限递归一样)。

      也许你的 shell 没有报告段错误。您使用的是什么操作系统?

      【讨论】:

        【解决方案6】:

        在过去,当您想要过度优化 ASM 程序时,有一种做法:有时函数会以调用另一个函数(然后返回)而结束。它看起来像:

        somefunc:
        
            ; do some things
        
            CALL someotherfunc
            RET
        
        someotherfunc:
        
            ; do some other things
        
            RET
        

        这样当CALL someotherfunc发生时,下一条指令的地址(RET)被保存到堆栈中,然后someotherfunc返回只是为了执行a return。使用JMPsomeotherfunc 可以获得完全相同的结果。这样堆栈将不包含最后一条指令的地址,但会包含原始调用者的地址。所以当someotherfunc 变成RET 时,程序会在原来的调用者处继续。

        所以优化后的代码如下所示:

        somefunc:
        
            ; do some things
        
            JMP someotherfunc
        
        someotherfunc:
        
            ; do some other things
        
            RET
        

        如果somefunc 将自己称为最后一条指令(事实上这是唯一的指令),它确实看起来像:

        somefunc:
        
            JMP somefunc
        

        【讨论】:

          猜你喜欢
          • 2010-11-27
          • 1970-01-01
          • 1970-01-01
          • 2016-02-19
          • 2012-05-28
          • 1970-01-01
          • 2016-08-17
          相关资源
          最近更新 更多