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

时间:2022-09-13 00:37:45

Consider following program:

考虑以下项目:

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

Using g++ 4.8.1 (mingw64) on Windows 7 OS, the program compiles and runs fine, printing:

在Windows 7操作系统上使用g++ 4.8.1 (mingw64),程序可以很好地编译和运行,打印如下:

C++ is excellent!

c++是优秀的!

to the console. main appears to be a global variable rather than a function; how can this program execute without the function main()? Does this code conform to the C++ standard? Is the behavior of the program is well defined? I have also used the -pedantic-errors option but the program still compiles and runs.

到控制台。main似乎是全局变量,而不是函数;如果没有函数main(),这个程序怎么执行?这段代码是否符合c++标准?程序的行为是否定义良好?我还使用了- pedan- error选项,但程序仍在编译和运行。

7 个解决方案

#1


81  

Before going into the meat of the question about what is going on, it is important to point out that program is ill-formed as per defect report 1886: Language linkage for main():

在讨论关于正在发生什么问题的问题之前,必须指出的是,程序是根据1886年的缺陷报告而形成的:主要()的语言链接:

[...] A program that declares a variable main at global scope or that declares the name main with C language linkage (in any namespace) is ill-formed. [...]

[…在全局作用域中声明一个变量main的程序,或者使用C语言链接(在任何名称空间中)声明名称main的程序都是错误的。[…]

The most recent versions of clang and gcc makes this an error and the program will not compile (see gcc live example):

最新版本的clang和gcc使这成为一个错误,程序将不会编译(参见gcc live示例):

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

So why was there no diagnostic in older versions of gcc and clang? This defect report did not even have a proposed resolution until late 2014 and so this case was only very recently explicitly ill-formed, which requires a diagnostic.

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

Prior to this, it seems like this would be undefined behavior since we are violating a shall requirement of the draft C++ standard from section 3.6.1 [basic.start.main]:

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

A program shall contain a global function called main, which is the designated start of the program. [...]

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

Undefined behavior is unpredictable and does not require a diagnostic. The inconsistency we see with reproducing the behavior is typical undefined behavior.

未定义的行为是不可预测的,不需要诊断。复制行为的不一致性是典型的未定义行为。

So what is the code actually doing and why in some cases does it produce results? Let's see what we have:

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

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

We have main which is an int declared in the global namespace and is being initialized, the variable has static storage duration. It is implementation defined whether the initialization will take place before an attempt to call main is made but it appears gcc does do this before calling main.

我们有main,它是在全局命名空间中声明的,并且正在初始化,变量具有静态存储时间。实现定义了初始化是否在尝试调用main之前进行,但似乎gcc在调用main之前会这样做。

The code uses the comma operator, the left operand is a discarded value expression and is used here solely for the side effect of calling std::cout. The result of the comma operator is the right operand which in this case is the prvalue 195 which is assigned to the variable main.

代码使用逗号操作符,左操作数是一个丢弃的值表达式,在这里仅用于调用std:::cout的副作用。逗号运算符的结果是正确的操作数,在本例中是prvalue 195,它被分配给变量main。

We can see sergej points out the generated assembly shows that cout is called during static initialization. Although the more interesting point for discussion see live godbolt session would be this:

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

main:
.zero   4

and the subsequent:

和随后的:

movl    $195, main(%rip)

The likely scenario is that the program jumps to the symbol main expecting valid code to be there and in some cases will seg-fault. So if that is the case we would expect storing valid machine code in the variable main could lead to workable program, assuming we are located in a segment that allows code execution. We can see this 1984 IOCCC entry does just that.

可能的情况是,程序跳转到符号main,期望有效代码在那里,在某些情况下会出现segue -fault。因此,如果是这样的话,我们将期望在变量main中存储有效的机器代码,这将导致可操作的程序,假设我们位于允许代码执行的段中。我们可以看到,这个1984年的IOCCC词条就是这么做的。

It appears we can get gcc to do this in C using (see it live):

看来我们可以用C语言让gcc来做这个(现场看):

const int main = 195 ;

It seg-faults if the variable main is not const presumably because it is not located in an executable location, Hat Tip to this comment here which gave me this idea.

如果变量main不是const,可能是因为它不在可执行位置,那么它就会出现故障。

Also see FUZxxl answer here to a C specific version of this question.

也可以在这里看到FUZxxl对这个问题的C特定版本的回答。

#2


19  

From 3.6.1/1:

从3.6.1/1:

A program shall contain a global function called main, which is the designated start of the program. It is implementation defined whether a program in a freestanding environment is required to define a main function.

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

From this it looks like g++ happens to allow a program (presumably as the "freestanding" clause) without a main function.

从这里看来,g++碰巧允许一个程序(可能是“独立的”子句)没有主函数。

Then from 3.6.1/3:

然后从3.6.1/3:

The function main shall not be used (3.2) within a program. The linkage (3.5) of main is implementation defined. A program that declares main to be inline or static is illformed. The name main is not otherwise reserved.

程序中不能使用函数main(3.2)。主要的链接(3.5)是已定义的实现。将main声明为内联或静态的程序是错误的。main名称未被保留。

So here we learn that it's perfectly fine to have an integer variable named main.

这里我们知道,有一个名为main的整数变量是很好的。

Finally if you're wondering why the output is printed, the initialization of the int main uses the comma operator to execute cout at static init and then provide an actual integral value to do the initialization.

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

#3


9  

gcc 4.8.1 generates the following x86 assembly:

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

Note that cout is called during initialization, not in the main function!

注意,在初始化期间调用cout,而不是在主函数中!

.zero 4 declares 4 (0-initialized) bytes starting at location main, where main is the name of the variable[!].

.zero 4声明4 (0-initialized)字节从main位置开始,main位置是变量的名字[!]。

The main symbol is interpreted as the start of the program. The behavior depends on the platform.

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

#4


8  

That is an ill-formed program. It crashes on my test environment, cygwin64/g++ 4.9.3.

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

From the standard:

从标准:

3.6.1 Main function [basic.start.main]

3.6.1主要功能(basic.start.main)

1 A program shall contain a global function called main, which is the designated start of the program.

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

#5


7  

The reason I believe this works is that the compiler does not know it is compiling the main() function so it compiles a global integer with assignment side-effects.

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

The object format that this translation-unit is compiled into is not capable of differentiating between a function symbol and a variable symbol.

这个平移单元编译成的对象格式不能区分函数符号和变量符号。

So the linker happily links to the (variable) main symbol and treats it like a function call. But not until the runtime system has run the global variable initialization code.

因此链接器高兴地链接到(变量)主符号并将其视为函数调用。但是直到运行时系统运行全局变量初始化代码之后。

When I ran the sample it printed out but then it caused a seg-fault. I assume that's when the runtime system tried to execute an int variable as if it were a function.

当我运行这个样本时,它打印出来了,但是它导致了一个错误。我假设运行时系统在执行int变量时就像执行一个函数一样。

#6


4  

I've tried this on a Win7 64bit OS using VS2013 and it compiles correctly but when I try to build the application I get this message from the output window.

我使用VS2013在win764bit操作系统上尝试过,它编译正确,但是当我尝试构建应用程序时,我从输出窗口获得了这条消息。

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

#7


-1  

You are doing tricky work here. As main( somehow) could declared to be integer. You used list operator to print message & then assign 195 to it. As said by someone below, that it doesn't comfort with C++, is true. But as compiler didn't find any user defined name, main, it didn't complaint. Remember main is not system defined function, its user defined function & thing from which program starts executing is Main Module, not main(), specifically. Again main() is called by startup function which is executed by loader intentionally. Then all of your variables are initialized, & while initializing it output like that. That's it. Program without main() is ok, but not standard.

你在做棘手的工作。因为main(以某种方式)可以声明为整数。您使用列表操作符打印消息,然后为其分配195。正如下面有人所说的,它不适合c++,这是事实。但是由于编译器没有找到任何用户定义的名称main,它没有抱怨。记住main不是系统定义函数,它的用户定义函数&程序开始执行的东西是主模块,而不是main()。main()由startup函数调用,加载程序故意执行这个函数。然后所有的变量都被初始化&同时初始化输出。就是这样。没有main()的程序是可以的,但不是标准的。

#1


81  

Before going into the meat of the question about what is going on, it is important to point out that program is ill-formed as per defect report 1886: Language linkage for main():

在讨论关于正在发生什么问题的问题之前,必须指出的是,程序是根据1886年的缺陷报告而形成的:主要()的语言链接:

[...] A program that declares a variable main at global scope or that declares the name main with C language linkage (in any namespace) is ill-formed. [...]

[…在全局作用域中声明一个变量main的程序,或者使用C语言链接(在任何名称空间中)声明名称main的程序都是错误的。[…]

The most recent versions of clang and gcc makes this an error and the program will not compile (see gcc live example):

最新版本的clang和gcc使这成为一个错误,程序将不会编译(参见gcc live示例):

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

So why was there no diagnostic in older versions of gcc and clang? This defect report did not even have a proposed resolution until late 2014 and so this case was only very recently explicitly ill-formed, which requires a diagnostic.

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

Prior to this, it seems like this would be undefined behavior since we are violating a shall requirement of the draft C++ standard from section 3.6.1 [basic.start.main]:

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

A program shall contain a global function called main, which is the designated start of the program. [...]

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

Undefined behavior is unpredictable and does not require a diagnostic. The inconsistency we see with reproducing the behavior is typical undefined behavior.

未定义的行为是不可预测的,不需要诊断。复制行为的不一致性是典型的未定义行为。

So what is the code actually doing and why in some cases does it produce results? Let's see what we have:

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

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

We have main which is an int declared in the global namespace and is being initialized, the variable has static storage duration. It is implementation defined whether the initialization will take place before an attempt to call main is made but it appears gcc does do this before calling main.

我们有main,它是在全局命名空间中声明的,并且正在初始化,变量具有静态存储时间。实现定义了初始化是否在尝试调用main之前进行,但似乎gcc在调用main之前会这样做。

The code uses the comma operator, the left operand is a discarded value expression and is used here solely for the side effect of calling std::cout. The result of the comma operator is the right operand which in this case is the prvalue 195 which is assigned to the variable main.

代码使用逗号操作符,左操作数是一个丢弃的值表达式,在这里仅用于调用std:::cout的副作用。逗号运算符的结果是正确的操作数,在本例中是prvalue 195,它被分配给变量main。

We can see sergej points out the generated assembly shows that cout is called during static initialization. Although the more interesting point for discussion see live godbolt session would be this:

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

main:
.zero   4

and the subsequent:

和随后的:

movl    $195, main(%rip)

The likely scenario is that the program jumps to the symbol main expecting valid code to be there and in some cases will seg-fault. So if that is the case we would expect storing valid machine code in the variable main could lead to workable program, assuming we are located in a segment that allows code execution. We can see this 1984 IOCCC entry does just that.

可能的情况是,程序跳转到符号main,期望有效代码在那里,在某些情况下会出现segue -fault。因此,如果是这样的话,我们将期望在变量main中存储有效的机器代码,这将导致可操作的程序,假设我们位于允许代码执行的段中。我们可以看到,这个1984年的IOCCC词条就是这么做的。

It appears we can get gcc to do this in C using (see it live):

看来我们可以用C语言让gcc来做这个(现场看):

const int main = 195 ;

It seg-faults if the variable main is not const presumably because it is not located in an executable location, Hat Tip to this comment here which gave me this idea.

如果变量main不是const,可能是因为它不在可执行位置,那么它就会出现故障。

Also see FUZxxl answer here to a C specific version of this question.

也可以在这里看到FUZxxl对这个问题的C特定版本的回答。

#2


19  

From 3.6.1/1:

从3.6.1/1:

A program shall contain a global function called main, which is the designated start of the program. It is implementation defined whether a program in a freestanding environment is required to define a main function.

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

From this it looks like g++ happens to allow a program (presumably as the "freestanding" clause) without a main function.

从这里看来,g++碰巧允许一个程序(可能是“独立的”子句)没有主函数。

Then from 3.6.1/3:

然后从3.6.1/3:

The function main shall not be used (3.2) within a program. The linkage (3.5) of main is implementation defined. A program that declares main to be inline or static is illformed. The name main is not otherwise reserved.

程序中不能使用函数main(3.2)。主要的链接(3.5)是已定义的实现。将main声明为内联或静态的程序是错误的。main名称未被保留。

So here we learn that it's perfectly fine to have an integer variable named main.

这里我们知道,有一个名为main的整数变量是很好的。

Finally if you're wondering why the output is printed, the initialization of the int main uses the comma operator to execute cout at static init and then provide an actual integral value to do the initialization.

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

#3


9  

gcc 4.8.1 generates the following x86 assembly:

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

Note that cout is called during initialization, not in the main function!

注意,在初始化期间调用cout,而不是在主函数中!

.zero 4 declares 4 (0-initialized) bytes starting at location main, where main is the name of the variable[!].

.zero 4声明4 (0-initialized)字节从main位置开始,main位置是变量的名字[!]。

The main symbol is interpreted as the start of the program. The behavior depends on the platform.

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

#4


8  

That is an ill-formed program. It crashes on my test environment, cygwin64/g++ 4.9.3.

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

From the standard:

从标准:

3.6.1 Main function [basic.start.main]

3.6.1主要功能(basic.start.main)

1 A program shall contain a global function called main, which is the designated start of the program.

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

#5


7  

The reason I believe this works is that the compiler does not know it is compiling the main() function so it compiles a global integer with assignment side-effects.

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

The object format that this translation-unit is compiled into is not capable of differentiating between a function symbol and a variable symbol.

这个平移单元编译成的对象格式不能区分函数符号和变量符号。

So the linker happily links to the (variable) main symbol and treats it like a function call. But not until the runtime system has run the global variable initialization code.

因此链接器高兴地链接到(变量)主符号并将其视为函数调用。但是直到运行时系统运行全局变量初始化代码之后。

When I ran the sample it printed out but then it caused a seg-fault. I assume that's when the runtime system tried to execute an int variable as if it were a function.

当我运行这个样本时,它打印出来了,但是它导致了一个错误。我假设运行时系统在执行int变量时就像执行一个函数一样。

#6


4  

I've tried this on a Win7 64bit OS using VS2013 and it compiles correctly but when I try to build the application I get this message from the output window.

我使用VS2013在win764bit操作系统上尝试过,它编译正确,但是当我尝试构建应用程序时,我从输出窗口获得了这条消息。

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

#7


-1  

You are doing tricky work here. As main( somehow) could declared to be integer. You used list operator to print message & then assign 195 to it. As said by someone below, that it doesn't comfort with C++, is true. But as compiler didn't find any user defined name, main, it didn't complaint. Remember main is not system defined function, its user defined function & thing from which program starts executing is Main Module, not main(), specifically. Again main() is called by startup function which is executed by loader intentionally. Then all of your variables are initialized, & while initializing it output like that. That's it. Program without main() is ok, but not standard.

你在做棘手的工作。因为main(以某种方式)可以声明为整数。您使用列表操作符打印消息,然后为其分配195。正如下面有人所说的,它不适合c++,这是事实。但是由于编译器没有找到任何用户定义的名称main,它没有抱怨。记住main不是系统定义函数,它的用户定义函数&程序开始执行的东西是主模块,而不是main()。main()由startup函数调用,加载程序故意执行这个函数。然后所有的变量都被初始化&同时初始化输出。就是这样。没有main()的程序是可以的,但不是标准的。