一个名为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初始化)字节,其中main
是variables[!]的名称。
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()的程序可以,但不是标准的。