为什么C++中的main()函数不能被内联?

70

我正在阅读C++常见问题解答,注意到其中一句话。

main()函数不能是内联的。

为什么会这样?


56
对我来说更有趣的问题是:为什么有人想要尝试内联它? - RiaD
7
要将主函数嵌入操作系统内核代码中吗? :) - Mehran
21
这很荒谬,对吧?内联是指将方法内容直接嵌入到调用代码中,而不是作为单独的方法存在。这意味着您需要重新编译操作系统,才能将main函数编译到其中。所以答案是因为您无法重新编译操作系统? - Kieren Johnstone
3
@Kieren:这就是为什么你永远不想在物理上内联函数。这并不严格等同于为什么函数不应标记为“inline”(请记住,这只是一个提示!)。 - Lightness Races in Orbit
5
把这个放在C++常见问题解答里似乎有点傻,因为你为什么想要那样做呢?就好像你看到一个针对一个毫无意义情境的警告标签一样。 - jhocking
显示剩余5条评论
17个回答

105
在C++中,调用您的代码中的main函数是不合法的,因此它永远无法被内联。

17
这就是原因,好好想一想。 - Tamás Szelei
17
@iammilind说:*static_cast<int*>(0) = 10也可以编译,但这并不意味着它是正确的……就像任何ODR违规一样,以及许多其他情况一样……它能够编译并不意味着它是合法的程序。 - David Rodríguez - dribeas
5
“它可以编译”这个说法需要上下文。因为按照标准,并不一定要求它必须编译通过,而事实上,在某些情况下它并不能编译通过。 - Benjamin Lindley
4
叹气 对于任何想知道的人,原帖中有位用户在评论中问了“为什么”,然后我回复了他的评论,但他删除了自己的评论。这样做并不好,楼主。 - Tamás Szelei
4
@sepp2k:看我的回答。简而言之,机器代码内联与问题无关,但从技术上讲,可以通过运行时库的调用以两种不同的方式进行内联。然而,由于没有优势,因此不会这样做。 :-) - Cheers and hth. - Alf
显示剩余12条评论

66

因为标准这样规定:

[2003:3.6.1/3]: 程序中不应使用函数main(3.2)。main的链接(3.5)是由实现定义的。声明main为内联或静态的程序是非法的。名称main并没有被保留。【例如:成员函数、类和枚举可以被称为main,其他命名空间中的实体也可以。】

为什么要这样规定呢?因为它试图将main的实现留给个别..好吧,实现者..尽可能多的自由决定,而不想限制实现只能在这里使用inline,因为从实际上来看,这样做没有什么实际好处。


委员会中的朋友证实了这一点:

原则上讲,没有理由一个inlinemain()不能工作。[..]我可以有一个C++解释器,可以调用内联的main()。[..]禁止使用inline/static main()是为了避免混淆。我很难想象这个原则会有什么额外的解释,超出了[这个问答]中已经提到的内容。


顺便说一句,不要将inline提示关键字与实际内联函数混淆。你可以标记一个函数为inline,但它可能并不是真正物理上的内联。

因此,即使main“不能被内联”是真的(严格来说这并不是真的,虽然像其他答案中解释的那样内联main会非常笨拙和无意义),从理论上讲,它仍然可以很好地支持inline提示关键字。

出于上述原因以及litb的回答,它并没有作用:这将会增加复杂性,却没有真正的好处。


4
引用标准值得点赞。然而,这可能并不能完全回答提问者的问题;到目前为止,我还没有看到任何反对的合理答案,除了你的帖子。 - Thomas Matthews
关于“不想通过要求内联有效来限制实现”的问题,支持inline用于main是微不足道的,因为它可以被忽略,因此不会限制任何实现,因此,标准禁止的这个可能原因站不住脚。抱歉,但我没有更多的答案可以提供了,我认为在inline方面我们是同意的,这是毫无意义的。 - Cheers and hth. - Alf
@Cheersandhth.-Alf:这有点意味着,如果所有定义在词法上都相同(在其他限制条件下),你可以在多个TUs中定义main,但这样做没有太多意义,因此不值得允许。 - Lightness Races in Orbit
@LightnessRacesinOrbit:main函数的链接是什么意思?为什么它是实现定义的? - Destructor
2
@meet:为什么不应该这样呢?与用户定义的其他函数不同,main具有必须与实现的运行时和主机操作系统交互的含义(因为它是程序入口点),因此对于一个委员会强制规定太多关于它的事情是没有意义的。请记住,其他函数的链接是由用户定义的,因此,事实上,标准在这里略微限制了main,它说“听你的编译器供应商的,因为他们可以选择这个而不是你”。 :) - Lightness Races in Orbit
显示剩余3条评论

27
C运行时库需要找到这个符号,以便“知道”要运行哪个函数。

这是否意味着链接器无法找到其他内联函数的符号? - Thomas Matthews
@Thomas Matthews:这取决于链接器的智能程度。一般来说,链接器不知道内联函数;它们具有内部链接性。更现代化的链接器要聪明一些,它们会尝试进行整个程序的优化,这是一个完全不同的游戏规则。 :) - Billy ONeal
我还想说,在C运行时通常会显式调用main()函数,并且几乎总是动态链接的。因此,在典型情况下,它根本不可能工作。 - whitequark

17

在C++中禁止直接调用main()函数,因此对它进行内联没有任何意义。


5
“没有意义”不足以完全禁止某事。情况比这稍微复杂一些。 - Lightness Races in Orbit

14

通常情况下,main()函数从系统的init()函数中调用。因此,需要确保main()唯一一个定义

现在,如果我们可以将main()函数inline并包含在头文件中,那么每个翻译单元都会有不同的main()定义。这是不被允许的。您可以在namespace中声明main()并对其进行inline。但不能在全局范围内使用main()


你也可以不使用 inline 来实现这个。 - Lightness Races in Orbit
不过如果每个定义都有内部链接,则无所谓。(注意,这就是为什么static int main()也是不好的形式:D) - Lightness Races in Orbit
一个函数也可以内联,只有一个实例。编译器和链接器可以识别多个 main 函数的实例,那么你的观点是什么? - Thomas Matthews
考虑将函数指针分配给内联函数的情况。指针指向哪个实例?必须有一个实例来满足此条件。同样,只能有一个main实例的限制仍然存在。尽管内联版本的main可能是多余或无用的,但我认为没有理由不允许它。 - Thomas Matthews
@iammilind:这并不是“等价的”。一个被禁止,而另一个则没有。 - Lightness Races in Orbit
显示剩余3条评论

10

首先,您必须了解如何使用内联函数。

例如:

 inline void f() {
     int a  = 3;
     a += 3;
     cout << a;
 }

 int main() {
      f();
      return 0;
 }

编译器将会把它看作:

 int main() {
        int a  = 3;
        a += 3;
        cout << a;
        return 0;
 }

看这个例子,你想如何使主函数立即内联化?


@the_drow:我希望nirmus能看到这个并自己思考修复方法!不过还是谢谢你! - Lightness Races in Orbit
2
那么处理只有一个调用的inline函数和只有一个调用的main函数有什么区别呢? - Thomas Matthews
这个方法是立即内联的。不是真的。inline只是一个提示。它不执行函数内联。 - Lightness Races in Orbit

7
有人指出,在机器码级别上调用main函数是无法有意义地内联的。这是荒谬的。虽然这需要链接器的一些帮助(例如全局优化),或者重新编译运行时库的一部分才能适用于每个应用程序,但这是完全可行的,没有技术问题。
然而,对于一个只被调用一次且在控制的顶层的函数(如main),inline的提示效果是无关紧要的。 inline的唯一保证作用是允许在两个或多个翻译单元中定义(相同的)具有外部链接的函数,即影响一个定义规则。
实际上,这允许将定义放置在头文件中,并且将其放置在头文件中也是实际上必要的以保证相同的定义。
这对于main来说是不合理的,因此没有理由使main成为inline

1
main 没有必要被标记为 inline” 这个说法很有说服力,但并不能直接解释为什么它被设计成无法被标记为 inline - Lightness Races in Orbit
1
@匿名的点踩者:请解释一下你们点踩的原因,这样其他人也可以从你们的见解中受益(呵呵)。 - Cheers and hth. - Alf
我不同意在头文件中定义main函数没有意义的说法。有很多库/框架包含了main函数的实现,当链接时会间接调用你的代码。从先验上看,我并没有看到在头文件中定义main函数和这种情况有显著的区别。 - Tim Seguine
@TimSeguine:我同意将main放在头文件中的做法,这个头文件只会被一个翻译单元包含。事实上,我已经这样做了(示例请见github)。但是,在多个翻译单元中保证相同的定义是没有意义的。在我看来,也没有其他任何理由需要应用inline - Cheers and hth. - Alf
类似于保证COMDAT折叠可能会很有用。但是由于标准没有保证,我不得不承认你是正确的。 - Tim Seguine
显示剩余4条评论

7
C++标准规定main函数不能被内联,参考@Tomalak Geret'kal的回答。如果标准中的限制被移除,则本回答讨论了内联main函数的可能性。 内联定义
inline关键字是向编译器建议将函数内容嵌入到当前位置的一种方法。目的之一是消除调用和返回函数(子程序)时存在的开销。
重要的内联情况是有指向函数的指针的情况。在这种情况下,必须有至少一个静态副本。此时,链接器可以解决内联函数的“外部链接”,因为有一个静态版本。
需要注意的是,编译器和链接器决定是否粘贴内容或调用函数的单个实例。
需要注意的是,编译器也可以将未被标记的函数内联。 内联主函数
由于只允许调用main一次,如何链接取决于编译器。标准允许单个实例的内联函数。编译器允许将内联函数转换为对单个实例的函数调用。因此,编译器会忽略对main函数的内联建议。
编译器和链接器必须确保只存在一个内联的main函数实例。这就是棘手的部分,特别是涉及外部链接时。确保只有一个实例的一种处理方式是留下某个翻译信息,表明该翻译存在“main”函数,无论它是否被内联。需要注意的是,当调用内联函数时,编译器可以从具有外部链接的符号表中删除该函数,因为其想法是该函数不会被外部函数调用。 总结
技术上讲,没有什么可以阻止内联main函数。已经存在将内联函数转换为单个实例的机制,并且可以识别函数的多个实例。当有指向内联函数的指针时,会创建函数的单个实例,因此它具有地址。此机制将满足main具有地址的运行时库要求。对于main函数的inline情况,它将被忽略,但不应该有任何理由阻止这种语法(除了让人困惑)。毕竟,已经存在一些冗余的语法,例如将按值(复制)传递的参数声明为const
“那只是我的个人观点,我可能是错的。”——喜剧演员丹尼斯·米勒。

6
您只能定义一次main。因此将inline放在其中不会起到任何作用 - inline仅在可以在程序中定义多次的函数上具有重要作用(所有定义将被视为只有一个定义,并且所有定义都需要相同)。
由于inline函数可以在程序中定义多次,并且inline还可以使对标记为inline的函数的调用尽可能快,因此标准要求在每个使用它的翻译单元中定义inline函数。因此,如果inline函数没有被当前翻译单元中的代码使用,则编译器通常会丢弃该函数的定义。对于main来说这样做是完全错误的,这表明inlinemain的语义是完全不兼容的。
请注意,您标题中的问题“为什么C++中的main()不能被内联?”和您引用的标准声明涉及不同的内容。您正在询问函数是否可以内联,这通常被理解为完全或部分地将被调用函数的代码插入调用函数中。仅将函数标记为inline并不意味着完全内联该函数。这完全取决于编译器的决定,当然,如果您从未调用main(您也无法这样做),那么就没有什么可以内联的了。

1
标准中的术语有些笨拙,但是虽然内联函数可以被多次定义,但所有定义必须相同,并且代码的行为必须像只定义了一次一样。 (内联函数必须在使用它的每个翻译单元中定义这一事实更加棘手。将使用它的唯一翻译单元是您没有编写的一个已经与您的系统一起编译的单元。) - James Kanze
2
@James:关于括号中的备注,是的,但实现可以做任何它想做的魔术。<g> - Cheers and hth. - Alf
@Alf同意,只要可观察的行为得以保持。但标准并不需要这样的魔法;允许main是内联的将需要它。从历史上看,C++不喜欢要求魔法。(但那是在模板之前。) - James Kanze

3
如果你静态链接到CRT并启用了一些链接时编译内联(例如MSVC),那么可能可以将其内联。但这真的没有意义。它只会被调用一次,与在main函数执行第一行之前完成的所有其他操作相比,该函数调用开销几乎为零。
...
而且,这是强制使符号仅出现一次在可执行文件中的简单方法。 :)

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