堆栈溢出错误或异常?

时间:2011-03-30 23:29:48

标签: c++ recursion

为什么以下结果没有错误?

void func()
{
   func();
}

int main()
{
   func();
}

6 个答案:

答案 0 :(得分:23)

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

这可以通过OP代码生成的examining the optimized assembly轻松看出:

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

func针对无限循环进行了优化,包括main中的“独立版本”和内联调用。

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

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

答案 1 :(得分:8)

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

答案 2 :(得分:5)

在我的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

答案 3 :(得分:3)

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

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

Multics在1966年做到了这一点。很遗憾我们在40多年里没有应用这些课程。

答案 4 :(得分:1)

在我的机器上,它以段错(如无限递归)结束。

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

答案 5 :(得分:1)

回到过去,当你想要过度优化ASM程序时,有一种做法:有时候函数会以调用其他函数(然后返回)结束。它看起来像是:

somefunc:

    ; do some things

    CALL someotherfunc
    RET

someotherfunc:

    ; do some other things

    RET

这种方式当CALL someotherfunc发生时,下一条指令的地址(RET)被保存到堆栈中,然后只返回someotherfunc来执行返回。使用JMPsomeotherfunc可以获得完全相同的结果。这样堆栈将不包含最后一条指令的地址,但它将包含原始调用方的地址。因此,当someotherfunc使RET成为somefunc: ; do some things JMP someotherfunc someotherfunc: ; do some other things RET 时,程序将在原始调用者处继续。

所以优化的代码看起来像:

somefunc

如果somefunc: JMP somefunc 将自己称为最后一条指令(实际上这是唯一的指令),它的确如下:

{{1}}