一个名为main而不是main函数的全局variables的程序如何工作?

考虑以下程序:

#include <iostream> int main = ( std::cout << "C++ is excellent!\n", 195 ); 

在Windows 7操作系统上使用g ++ 4.8.1(mingw64),程序编译并运行正常,打印:

C ++非常好!

到控制台。 main似乎是一个全局variables而不是一个函数; 这个程序如何在没有函数main()情况下执行? 这个代码是否符合C ++标准? 该scheme的行为是否明确? 我也使用了-pedantic-errors选项,但程序仍然编译和运行。

在讨论正在发生的事情之前,必须指出的是,根据缺陷报告1886:main()的语言链接 ,程序是不合格的:

[…]在全局范围声明一个variablesmain的程序,或者用C语言链接(在任何名字空间中)声明名字main的程序是不合格的。 […]

最新版本的clang和gcc使这个错误,程序不会编译( 请参阅gcc现场示例 ):

 error: cannot declare '::main' to be a global variable int main = ( std::cout << "C++ is excellent!\n", 195 ); ^ 

那么为什么在旧版本的gcc和clang中没有诊断? 这个缺陷报告直到2014年下半年才提出解决scheme,所以这个案例只是在最近才明确的形成,这需要一个诊断。

在此之前,似乎这将是未定义的行为,因为我们违反3.6.1[basic.start.main]中对C ++标准草案的要求:

程序应该包含一个名为main的全局函数,它是程序的指定开始。 […]

未定义的行为是不可预知的,不需要诊断。 我们看到的复制行为的不一致性是典型的未定义的行为。

那么代码实际上在做什么以及为什么在某些情况下会产生结果呢? 让我们看看我们有什么:

 declarator | initializer---------------------------------- | | | vvv int main = ( std::cout << "C++ is excellent!\n", 195 ); ^ ^ ^ | | | | | comma operator | primary expression global variable of type int 

我们main是在全局名字空间中声明的一个int ,并且正在被初始化,这个variables具有静态存储持续时间。 它是实现定义初始化是否会在尝试调用main之前发生,但在调用main之前,gcc确实会这样做。

代码使用逗号运算符 ,左边的操作数是一个丢弃的值expression式,这里仅用于调用std::cout的副作用。 逗号运算符的结果是正确的操作数,在这种情况下是赋值给variablesmain的prvalue 195

我们可以看到sergej指出生成的程序集显示cout在静态初始化期间被调用。 虽然更有趣的讨论点看现场godbolt会议将是这样的:

 main: .zero 4 

随后:

 movl $195, main(%rip) 

可能的情况是,程序跳转到符号main期望有效的代码在那里,在某些情况下将seg-fault 。 所以如果是这样的话,我们可以预期在variablesmain存储有效的机器代码可能导致可行的程序 ,假设我们位于允许代码执行的段中。 我们可以看到, 1984年的IOCCC条目 就是这样做的 。

看来我们可以让gcc在C中使用( 见它现场 ):

 const int main = 195 ; 

它seg-fault如果variablesmain不是常量大概是因为它不在一个可执行的位置,帽子提示这里这个评论给了我这个想法。

另请参阅FUZxxl在这里回答这个问题的C特定版本。

从3.6.1 / 1:

程序应该包含一个名为main的全局函数,它是程序的指定开始。 它是实现定义是否需要在独立环境中的程序来定义主要function。

从这看起来像g ++恰好允许一个程序(大概是作为“独立”的条款)没有一个主要function。

然后从3.6.1 / 3:

function主体不能在程序中使用(3.2)。 主要的连接(3.5)是实现定义的。 声明main为内联或静态的程序是不合格的。 名称main不保留。

所以在这里我们知道有一个名为main的整数variables是完全正确的。

最后,如果您想知道为什么输出是打印的,则int main的初始化将使用逗号运算符在静态init中执行cout ,然后提供一个实际的整数值来执行初始化。

gcc 4.8.1生成下面的x86程序集:

 .LC0: .string "C++ is excellent!\n" subq $8, %rsp #, movl std::__ioinit, %edi #, call std::ios_base::Init::Init() # movl $__dso_handle, %edx #, movl std::__ioinit, %esi #, movl std::ios_base::Init::~Init(), %edi #, call __cxa_atexit # movl $.LC0, %esi #, movl std::cout, %edi #, call std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*) # movl $195, main(%rip) #, main addq $8, %rsp #, ret main: .zero 4 

请注意, cout在初始化过程中被调用,而不是在main函数中调用!

.zero 4声明从位置main开始的4个(0初始化)字节,其中mainvariables[!]的名称。

main符号被解释为程序的开始。 行为取决于平台。

这是一个不健全的程序。 它在我的testing环境下崩溃,cygwin64 / g ++ 4.9.3。

从标准:

3.6.1主要function [basic.start.main]

1程序应该包含一个名为main的全局函数,它是程序的指定开始。

我相信这个原理的原因是编译器不知道它正在编译main()函数,所以它编译了一个带有分配副作用的全局整数。

这个翻译单元编译成的对象格式不能区分函数符号variables符号

所以链接器很高兴地链接到(variables) 符号,并把它看作一个函数调用。 但是,直到运行时系统运行全局variables初始化代码。

当我运行样品打印出来,但随后导致了一个seg-fault 。 我假设当运行时系统试图执行一个intvariables ,就像它是一个函数

我已经试过这在Win7 64位操作系统使用VS2013,它编译正确,但是当我尝试构build应用程序时,我从输出窗口得到这个消息。

 1>------ Build started: Project: tempTest, Configuration: Debug Win32 ------ 1>LINK : fatal error LNK1561: entry point must be defined ========== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ========== 

你在这里做了棘手的工作。 作为主(不知何故)可以宣布为整数。 你用列表操作符来打印消息,然后给它分配195。 正如下面的人所说,它不符合C ++的要求,这是事实。 但是,由于编译器没有find任何用户定义的名称,主,它没有投诉。 记住main不是系统定义的函数,它的用户定义的函数和程序开始执行的东西是Main Module,而不是main()。 再次main()被启动函数调用,由loader有意地执行。 然后,所有的variables都被初始化,并且像这样初始化输出。 而已。 没有main()的程序可以,但不是标准的。