为什么C ++中的main()无法内联?


69

我在阅读C ++常见问题解答,但发现一句话。

main()不能内联。

为什么是这样?


56
对我来说更有趣的问题:为什么有人想要内联?
RiaD

7
要在os内核代码中内联主代码?:)
Mehran

21
这很傻,对吗?内联是将方法的内容直接放置在调用代码中的位置,而不是单独的方法。这意味着您需要重新编译操作系统才能将main函数编译到其中。答案是,因为您无法重新编译操作系统?
基恩·约翰斯通

3
@Kieren:这就是为什么您不想物理内联该函数的原因。这与为什么标记功能并不完全相同inline(请记住,这只是一个提示!)。
Lightness Races in Orbit

5
把它放在C ++ FAQ中对我来说有点愚蠢,因为您为什么要这么做。就像当您看到警告标签说明没有任何意义的情况一样。
2011年

Answers:


105

在C ++中,在代码中调用main函数是不合法的,因此不可能内联。


17
就是原因,请考虑一下。
陶Szelei

17
@iammilind:也可以*static_cast<int*>(0) = 10编译,这并不意味着它是正确的...就像任何ODR违规以及许多其他事情一样...它可以编译的事实并不意味着它是一个合法程序。
大卫·罗德里格斯(DavidRodríguez)-dribeas 2011年

5
@iammilind:“编译”语句需要上下文。因为肯定不是标准要编译的,而且实际上并非在所有情况下都编译。
本杰明·林德利

4
对任何想知道的人都感叹,op在评论中“出于某种原因”问,然后我回答了他,但他删除了他。不酷,运
陶Szelei

4
@ sepp2k:看看我的答案。简而言之,机器代码内联与该问题无关,但是从技术上讲,可以通过两种不同的方式将其内联到运行时库的调用中。但是,这没有完成,因为没有优势。:-)
干杯和hth。-Alf

66

因为该标准是这样说的:

[2003: 3.6.1/3]:在程序中不得使用main函数(3.2)。main的链接(3.5)是实现定义的。声明main为嵌入式或静态的程序格式错误。名称main否则不保留。[示例:成员函数,类和枚举可以称为main,其他命名空间中的实体也可以称为main。]

为什么这么说呢?因为它试图将更多的实现留给main个人来做,所以,实现是尽可能的,并且不想通过inline在可能没有实际好处的情况下要求此处有效来限制实现。


我在委员会上的朋友证实了这一点:

inline main()本身没有任何理由不起作用。[..]我可能有一个C ++解释器,可以调用inlined main()。[..] [但是] inline/static main()是被禁止的,以期避免混乱。我很难想象,该原理将是在[此问与答]中已经说过的东西之外的任何东西。


顺便说一句,不要将inlinehint关键字与实际的内联函数混淆。您可以标记一个函数inline,但实际上可能没有内联。

因此,即使main“不能内联”的确是正确的(严格地说,这是正确的,尽管内联会main很笨拙且毫无意义,如其他答案所述),但理论上它仍inline可以很好地支持提示关键字。

这不是出于上述原因,而是在litb的回答中:它会使事情变得复杂,没有任何实际好处。


4
+1用于引用标准。但是,这可能无法完全回答OP的问题;到目前为止,除了您的帖子,我还没有看到任何合理的答案。
Thomas Matthews

@Thomas:我给出的基本原理与其他答案中的基本相同,只是关于为什么可能没有实际好处的细节较少。:)
轻轨比赛将于

重新“并不想限制实现通过要求行内是有效的”,配套inlinemain是平凡的,因为它正好可以忽略不计,因此,不限制任何实现的,因此,这可能的原因为标准的禁令是站不住脚的。抱歉。但是我提供的无非是我的回答,那就是没有任何意义inline(对此,我们认为我认为是正确的)。
干杯和健康。-阿尔夫

@ Cheersandhth.-Alf:这有点暗示您可以main在多个TU中进行定义,如果这些定义在词法上都是相同的(除其他限制之外),那么意义不大,值得禁止。
Lightness Races in Orbit

2
@meet:为什么不应该呢?与用户定义的其他功能不同,main它的含义必须与实现的运行时以及与主机操作系统进行交互(因为它是程序的入口点),因此,由人组成的委员会对其进行过多的授权是没有意义的。回想一下,其他功能键联是用户自定义等,其实,标准制约main略有这里,说:“听你的编译器供应商,因为他们开始选择这个不是你”。:)
轻轨赛将于

27

C运行时库需要找到该符号,以便“知道”要运行的功能。


这是否意味着链接器找不到其他内联函数的符号?
Thomas Matthews

@Thomas Matthews:取决于链接器的智能程度。一般来说,不,链接器不知道内联函数;他们有内部联系。更加现代的链接器会更聪明,因为它们将尝试进行整个程序优化,这是完全不同的过程。:)
Billy ONeal

我还要说的是,在C运行时中(通常)有call对该main()函数的显式显示,并且几乎总是动态链接。因此,在典型情况下,它根本无法工作。
whitequark 2011年


14

通常main()从系统init()函数中调用。因此,需要为定义一个准确的定义main()

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


您也可以这样做inline
Lightness Races in Orbit

@Tomalak,则将导致多个定义错误。是不是
iammilind

如果每个定义都具有内部链接,则不会。(请注意,这也是为什么static int main()格式不正确的原因:D)
Lightness在Orbit的比赛中,

@Tomlak,是的。static int main()等价于namespace { int main() }。我已经在答案中提到了。
iammilind 2011年

一个人也可以内联一个函数,只有一个实例。编译器和链接器可以标识该main函数的多个实例,那么您的意思是什么?
Thomas Matthews

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仅具有一次调用的d函数和仅具有一次调用的main函数之间有什么区别?
Thomas Matthews

This method is inline immediately.不对。inline仅是一个提示。它不执行功能内联。
Lightness Races in Orbit

7

C ++标准表示main,根据@Tomalak Geret'kal的回复,不能内联该函数。该响应讨论main了删除标准中的限制时内联函数的可能性。

内联
定义inline关键字是对编译器的建议,可将其内容原位粘贴。一种目的是消除存在于从函数(子例程)调用和返回中的开销。

内联的重要情况是存在指向函数的指针的情况。在这种情况下,必须至少有一个该功能的静态副本。在这种情况下,链接器可以解析内联函数的“外部链接”,因为存在一个静态版本。

重要的是要注意,编译器和链接器确定是粘贴内容还是调用函数的单个实例。

还要注意的是,编译器也可以内联未编程人员未标记的功能。

内联主要功能
由于仅main允许一次调用,因此如何链接取决于编译器。标准允许内联函数的单个实例。允许编译器将inlined函数转换为对单个实例的函数调用。因此,编译器将忽略对该main函数的内联建议。

编译器和链接器必须确保仅main存在一个内联函数的实例。这是棘手的部分出现的地方,尤其是在外部链接中。确保一个实例的一种过程是让翻译具有“主”功能(无论是否内联)。 注意:调用内联函数时,允许编译器从符号表中删除该函数以进行外部链接,因为这样的想法是外部函数不会调用该函数。

总结
从技术上讲,没有什么可以阻止该main函数被内联的。的机械已存在用于将内联的函数成单实例和用于识别功能的多个实例。当存在指向内联函数的指针时,将创建一个函数的单个实例,因此它具有一个地址。该机器将满足运行时库对main具有地址的要求。在inlineformain函数的情况下,它将被忽略,但不应有任何理由阻止这种语法(除非使人感到困惑)。毕竟,已经有多余的语法案例,例如将通过值(副本)传递的参数声明为const

“那只是我的看法,我可能是错的。” -喜剧演员丹尼斯·米勒(Dennis Miller)。


7

其他人指出,main在机器代码级别上不能有意义地内联的调用。那是垃圾 这将需要链接程序提供一些帮助(例如全局优化),或者需要按应用程序重新编译运行时库的一部分,但这是完全可行的,这里没有技术问题。

但是,调用的提示效果inline最好应该是内联的,与仅在控件的最高级别被调用一次的函数无关main

唯一保证效果inline是,以允许在两个或多个翻译单元(相同)中所定义的外部联动功能,即影响了一个定义规则。

实际上,这允许将定义放置在头文件中,并且将其放置在头文件中实际上也是保证相同定义的必要条件。

这不会使意义main,所以没有理由maininline


1
“没有理由maininline”是迫不得已的,但不是直接的解释,为什么它已经取得使其不能被标记inline
Lightness Races in Orbit

我在回答中也解释了同样的事情。但是,您的操作更为详尽。
iammilind

好吧,我认为,该标准并不能完全支持任何人都不会使用的东西。但我认为除此之外,对的描述main并不完美。例如,我一直认为并且仍然认为“ main的第一个语句之后”位从根本上是错误的。但是我从未见过任何地方讨论它。也许,这只是我对英语的不完全理解...
干杯和hth。-Alf

1
@匿名拒绝投票者:请说明您拒绝投票的原因,以便其他人可以从您的见解中受益(他)。
干杯和健康。-Alf

@Alf:类似地,除非有适当的理由,否则该标准不会竭尽全力地禁止事物。:)
轻轨赛将于

6

您只能定义main一次。因此,放置inline将无济于事-inline仅对您可以在程序中多次定义的功能有重要意义(所有定义都将被视为只有一个定义,并且所有定义必须相同)。

因为inline可以在程序中多次定义函数,并且inline还具有inline尽可能快地调用带标记的函数的目的,所以该标准要求inline在使用该函数的每个翻译单元中都定义函数。因此,如果inline当前翻译单元中的代码未使用该函数的定义并且该函数未使用该函数,则编译器通常会放弃该函数的定义。这样做main完全是错误的,这表明inline语义与语义main完全不兼容。

请注意,标题中的问题“为什么不能内联C ++中的main()?” 您引用的标准声明涉及的是不同的内容。您正在询问该函数是否可以内联,这通常被理解为将被调用函数的代码完全或部分插入到调用函数中。仅标记一个函数inline并不意味着完全内联该函数。这完全是编译器的决定,当然,如果您从不调用main(并且您不能这样做),则没有任何内容可以内联。


1
标准中的术语有些尴尬,但是可以对内联函数进行多重定义是正确的,但所有定义必须相同,并且代码的行为必须就像仅定义一次一样。(事实上​​,必须在使用该函数的每个翻译单元中都定义一个内联函数,这有点麻烦。唯一使用该函数的翻译单元是您未编写的,已随系统一起提供。)
James Kanze

2
@James:是的,是的,但是允许该实现程序执行其想要的任何魔术。<g>
干杯和健康。-Alf

@Alf同意,只要保持可观察的行为即可。但是标准不需要任何魔术。允许main内联将需要它。从历史上看,C ++不喜欢要求魔术。(但这是在模板之前。)
James Kanze 2011年

3

如果您静态链接到CRT启用了一些链接时编译内联(如MSVC那样),则可以内联它。

但这没有任何意义。它只会被调用一次,与main执行第一行之前完成的所有其他操作相比,该函数的调用开销几乎为零。

...

Aaand,这是一种强制符号仅在可执行文件中出现一次的简便方法。:)


2

有许多基本原因。基本上,main从运行时的基本初始化例程中调用,并且仅从那里开始。该代码(显然)是在不知道您的main内联的。现代编译器技术能够跨模块边界进行内联,但这是一项高级功能,许多较早的编译器均不支持。当然,仅当频繁调用函数时,内联的好处才存在。根据定义,main 将只被调用一次,不会更多,也不会更少。


2

我看到该标准是这样说的,但是真正的实际答案将很简单,就像声明添加到每个C和C ++程序的运行时必须调用可执行文件中的某个点一样。该函数应该有一个外部符号(运行时带有地址),以便链接程序可以在执行开始时找到要调用的函数。因此,您不能将其声明为inline,因为内联的编译器不会为其生成外部符号。


标记功能inline并不一定导致该功能内联。
Lightness Races in Orbit

一个inline函数有extern链接,除非它是一个的显式声明的一个命名空间范围功能static
干杯和健康。-Alf

1

因为它的main()函数开始执行,所以当代码被编译为二进制时,一切都在其main()自身中。所以你可以说,它已经内联了!

是的,在您的C ++程序中使用内联是非法的,这更多是关于语法!


1

对于大多数编译器/体系结构组合,main()源代码中的函数在最终二进制文件中成为合理的正常函数。这仅是因为在这些架构上很方便,而不是因为标准规定必须如此。

在受内存限制的体系结构上,许多编译器会生成平坦的二进制文件(如intex十六进制格式),而不是动态链接程序友好的容器(如elf或xcoff),它们会优化所有样板,因为这样做只会肿。有些体系结构根本不支持函数调用(在这些平台上只能使用C ++的有限子集。)

为了支持最广泛的此类架构和构建环境,该标准选择保留以下内容的语义: main()尽可能开放,以便编译器可以为最广泛的平台做正确的事。这意味着从整体上来说,该语言中的许多功能都无法应用于应用程序本身的启动和关闭。

如果您需要内联main()(或可重入,或任何精美的功能)之类的东西,您当然可以调用main函数:

inline int myMain(int argc, char **argv) { /* whatever */ }
int main(int argc, char **argv) { return myMain(argc, argv); }

1

内联函数默认情况下具有静态作用域。这意味着,如果我们将main()声明为内联,则其范围将限于定义该文件的文件。但是,C启动库(由编译器供应商提供)需要“ main”成为全局符号。有些编译器允许使用链接器标志修改入口点功能(例如main)。


0

内联函数通常没有地址,因此没有可移植的方式来调用main,main()需要一个可以将初始化代码跳转到的地址。内联函数应嵌入到调用函数中,如果main被内联,则应将其内联到程序的初始化代码中,该代码也不可移植。


在C ++程序中,没有可移植的方法来调用main,即使不是inline
柔印

-2

操作系统将二进制数据加载到内存中;寻找入口点(c / c ++中的“主要”符号);跳至入口点标签的地址。在未加载程序之前,操作系统对代码中的主要功能一无所知。


8
在大多数或可能所有系统上,不是操作系统负责调用main。而是,OS调用程序的机器代码级别入口点。对于C和C ++,该入口点通常是运行时库中的一个函数,它依次执行各种初始化工作,然后调用main,最后清理(例如,调用已安装的退出处理程序)并退出。
干杯和健康。-Alf
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.