在C++中,递归到main()函数是否合法?

45

我读到C++标准禁止在main()中进行递归,但是g++编译以下代码时没有任何警告:

int main()
{
    main();
}

有人能解释一下这个吗?


152
可以参考以下翻译:请参阅:https://dev59.com/zm855IYBdhLWcg3wIAka意思是询问在C语言中,在main函数中进行递归是否合法。 - Paul R
5
如果代码编译通过并不代表它合法,有可能是未定义的,这时就可能发生任何事情。 - BЈовић
2
@problemofficer: @VJo的评论是回复marcog的评论,而后者现在已经被删除了。(该评论是“如果编译通过,那肯定合法,不是吗?”或类似的话。) - T.J. Crowder
3
问题应该写成“在C++中,递归到main()是否合法?” - Ondra Žižka
3
@TonyK: void f() { f(); } int main() { f(); }你是在main()函数中进行递归吗?有些人会说“不是”,有些人会说“是”。你是在进入main()函数中进行递归吗?没有人会说“是”。因此,后者更清晰明了。 - j_random_hacker
显示剩余3条评论
5个回答

49
根据3.6.1/3标准,它不是:

在程序内部不应使用函数main(3.2)。

“使用”的定义为:

如果其名称出现在可能被评估的表达式中,则使用对象或非重载函数。


2
当然,这并不经常被强制执行,因为它只在你做的编程不太好时才有用。 - Donal Fellows
6
不需要诊断。请看1.4/1和1.4/2。GCC是错误的,有时它比允许的更过于追求细节。(链接为参考信息) - Johannes Schaub - litb
@Johannes:已删除可能允许编译它的gcc部分,感谢您的更正。 - icecrime

37

我会烹调鱼,并解释为什么这是被禁止的。在 C 或 C++ 程序开始运行之前,必须首先初始化 CRT(C 运行时库)。打开 stdin/out/err,调用初始化器等操作。有两种基本策略来完成此操作,这是一个繁重的平台实现细节。

  • 程序的起始地址指向 CRT 初始化函数,最终调用 main()。常见于具有支持可执行文件中任意部分的高级加载程序的完整操作系统。

  • 编译器将代码注入到 main() 函数中,以调用 CRT 初始化函数。起始函数始终为 main()。常见于具有受限制的加载程序功能的嵌入式平台。现在递归 main() 是个问题,CRT 启动代码将以不可预测的堆栈状态再次被调用。


你有相关的参考资料吗?我完全看不出为什么要将注入放在main之前而不是注入到main中。编译器无论如何都会生成机器代码,没有理由在使用main符号值作为地址后再这样做,无论可执行文件格式是否允许任意起始地址。 - T.J. Crowder
@T.J. 很难提供权威的实现细节链接。但是这个问题很好地展示了它:http://stackoverflow.com/questions/4355610/differences-in-dis-assembled-c-code-of-gcc-and-borland - Hans Passant
@Hans:谢谢。如果这是标准做法,并且它在像gcc这样的开源编译器中,我期望它已经被写成了某种权威的东西。我只是看不到这样做的好理由,当然也没有什么足够好的理由将实现限制注入到C++规范中。我的意思是,显然他们有一个理由,他们不是愚蠢的,我只是想知道是什么原因,因为我觉得起始地址参数并不令人信服。 - T.J. Crowder
@T.J. 如果你真的想知道,可以查看初始化 - Johannes Schaub - litb
1
@Johannes:谢谢。确实,这表明根据系统架构,gcc可能会这样做。它没有说明为什么(我仍然觉得这是难以理解的倒退),但对于OP的问题和这个答案,它所做的(以及这是被接受的做法)就足够了。再次感谢。 - T.J. Crowder
显示剩余2条评论

22

这里声称:http://www.programmersheaven.com/mb/CandCPP/109711/109711/recursive-main/,确实被禁止:

标准规定如下:

3.6.1.3
"在程序中不能使用函数main。"

5.2.2.9
"允许递归调用,但不可调用名为main的函数。"

当然,您可以这样做:

int main(int argc, char* argv[]) {
    return foo(argc, argv);
}
int foo(int argc, char* argv[]) {
    if (some_condition) {
        return foo(argc, argv);
    }
    return 0;
}

(注意,我加入了一个逃脱子句。即使是在假设情况下,我也无法编写无限递归的代码,因为它会对我重复执行。)


1
没有递归是假设的,因为没有栈是无限的。 ;) - Andrei Krotkov
3
如果你的函数是尾递归的,并且编译器进行了尾调用优化(大多数编译器都会这样做),那么可能会导致无限递归而不会溢出栈 ;-) - Sylvain Defresne

9
这是不合法的。请阅读3.6.1-3:
主函数不应在程序内使用(3.2)。主函数的链接(3.5)是实现定义的。声明主函数为内联或静态的程序是不合法的。名称main并没有被保留。例如:成员函数、类和枚举可以被称为main,其他命名空间中的实体也可以这样命名。

1

其他人已经解决了标准部分。然而,我想指出的是,如果您在至少一个这些错误中使用-pedantic-errors(取决于main签名),g++(至少4.6.2)将拒绝此操作:

error: ISO C++ forbids calling ‘::main’ from within program [-pedantic]
error: ISO C++ forbids taking address of function ‘::main’ [-pedantic]

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接