具有名为main的全局变量而不是main函数的程序如何工作?

时间:2015-09-29 18:23:52

标签: c++ main language-lawyer

考虑以下计划:

main

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

  

C ++非常棒!

到控制台。 main()似乎是一个全局变量而不是一个函数;如果没有函数-pedantic-errors,该程序如何执行?这段代码是否符合C ++标准?程序的行为是否定义明确?我还使用了 @Configuration @EnableWebMvc public class WebConfig extends WebMvcConfigurerAdapter { @Override public void configurePathMatch(PathMatchConfigurer configurer) { configurer.setUseRegisteredSuffixPatternMatch(true); } } 选项,但该程序仍在编译和运行。

7 个答案:

答案 0 :(得分:83)

在深入了解正在发生的事情的问题之前,重要的是要指出程序是defect report 1886: Language linkage for main()形式不正确的:

  

[...]在全局范围内声明变量main或者使用C语言链接(在任何命名空间中)声明名称main的程序是不正确的。 [...]

最新版本的clang和gcc使这个错误,程序无法编译( see gcc live example ):

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

那么为什么在旧版本的gcc和clang中没有诊断?这个缺陷报告甚至在2014年底之前都没有提出解决方案,因此这个案例最近才刚刚明确地形成,这需要进行诊断。

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

  

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

未定义的行为是不可预测的,不需要诊断。我们在重现行为时看到的不一致是典型的未定义行为。

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

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

我们在全局命名空间中声明了main int ,并且正在初始化,该变量具有静态存储持续时间。实现定义是否在尝试调用main之前进行初始化,但看起来gcc在调用main之前执行此操作。

代码使用comma operator,左操作数是废弃的值表达式,此处仅用于调用std::cout的副作用。逗号运算符的结果是右操作数,在这种情况下是分配给变量195的prvalue main

我们可以看到sergej points out生成的程序集显示在静态初始化期间调用了cout。虽然更有趣的讨论点see live godbolt session将是这样的:

main:
.zero   4

以及随后的:

movl    $195, main(%rip)

可能的情况是程序跳转到符号main,期望有效代码存在于some cases will seg-fault中。因此,如果是这种情况,我们希望在变量main中存储有效的机器代码可能会导致可用程序,假设我们位于允许代码执行的段中。我们可以看到this 1984 IOCCC entryjust that

看来我们可以使用gcc( see it live )在C中执行此操作:

const int main = 195 ;

如果变量main不是常量因为它不在可执行位置,那么它会出现段错误,这个comment here的帽子提示给了我这个想法。

另请参阅FUZxxl answer here此问题的C特定版本。

答案 1 :(得分:20)

从3.6.1 / 1:

  

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

从这看起来g ++恰好允许一个程序(大概是“独立”子句)没有主函数。

然后从3.6.1 / 3:

  

在程序中不得使用函数main(3.2)。该   主要的连接(3.5)是实现定义的。一个程序   声明main是inline或static是notformed。主要名称是   没有其他保留。

所以在这里我们知道有一个名为main的整数变量。

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

答案 2 :(得分:9)

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变量的名称[!]

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

答案 3 :(得分:8)

这是一个不正确的计划。它在我的测试环境崩溃,cygwin64 / g ++ 4.9.3。

来自标准:

  

3.6.1主要功能 [basic.start.main]

     

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

答案 4 :(得分:7)

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

转换单元编译成的对象格式无法区分函数符号变量符号

所以链接器很高兴地链接到(变量) main 符号,并将其视为函数调用。但直到运行时系统运行全局变量初始化代码。

当我运行样本时,它打印出来然后导致 seg-fault 。我假设当运行时系统尝试执行 int变量时,就像它是函数一样。

答案 5 :(得分:4)

我在使用VS2013的Win7 64位操作系统上尝试了这个并且它正确编译但是当我尝试构建应用程序时,我从输出窗口收到此消息。

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 ==========

答案 6 :(得分:-1)

你在这里做的很棘手。因为main(某种程度上)可以声明为整数。您使用列表运算符来打印消息&amp;然后给它分配195。正如下面的某个人所说,它对C ++并不安慰,这是真的。但由于编译器没有找到任何用户定义的名称,main,它没有投诉。记住main不是系统定义的功能,它的用户自定义功能&amp;程序开始执行的东西是Main Module,而不是main()。再次main()由启动函数调用,该函数由加载器有意执行。然后初始化所有变量,&amp;同时初始化它的输出。就是这样。没有main()的程序是可以的,但不是标准的。