为什么以下结果没有错误?
void func()
{
func();
}
int main()
{
func();
}
答案 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)
然而,在编译器中启用优化会将整个程序缩减为无限循环,当然,它根本不会结束:
.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
来执行返回。使用JMP
到someotherfunc
可以获得完全相同的结果。这样堆栈将不包含最后一条指令的地址,但它将包含原始调用方的地址。因此,当someotherfunc
使RET
成为somefunc:
; do some things
JMP someotherfunc
someotherfunc:
; do some other things
RET
时,程序将在原始调用者处继续。
所以优化的代码看起来像:
somefunc
如果somefunc:
JMP somefunc
将自己称为最后一条指令(实际上这是唯一的指令),它的确如下:
{{1}}