在C和C++中定义main()函数的正确(最有效)方法是什么?int main()或void main()-为什么?那争论呢?如果int main(),则返回1或0?


这个问题有很多重复之处,包括:

C的main()函数的有效签名是什么?main()函数的返回类型void main()和int main()之间的区别?main()在C中的签名++main()的正确声明是什么?-对于C++,确实有一个非常好的答案。C中main()函数的样式C中main()方法的返回类型C中的int main()与void main()

相关:

C++-int main(int argc,char**argv)C++-int main(int argc,char*argv[])char*envp[]作为main()的第三个参数是否可移植?int main()函数必须在所有编译器中返回值吗?为什么C和C++中main()函数的类型留给用户定义?为什么int main(){}编译?C++14中main()的合法定义?


当前回答

我认为main()应该返回EXIT_SUCCESS或EXIT_FAILURE。它们在stdlib.h中定义

其他回答

标准C-托管环境

对于托管环境(这是正常环境),C11标准(ISO/IEC 9899:2011)规定:

5.1.2.2.1程序启动程序启动时调用的函数名为main。实现声明没有此函数的原型。应使用返回类型int和no来定义参数:int main(void){/*…*/}或具有两个参数(此处称为argc和argv,但任何名称都可以是使用,因为它们是声明它们的函数的本地函数):int main(int argc,char*argv[]){/*…*/}或同等产品;10) 或者以某种其他实施方式定义的方式。如果声明了这些参数,则主函数的参数应符合以下要求约束条件:argc的值应为非负。argv[argc]应为空指针。如果argc的值大于零,则数组成员argv[0]到argv[argc-1](含)应包含字符串指针在程序启动之前由主机环境定义的实现值。这个目的是向程序提供程序启动前确定的信息来自托管环境中的其他地方。如果主机环境无法为字符串提供大小写字母,实现应确保以小写形式接收字符串。如果argc的值大于零,则argv[0]指向的字符串表示程序名称;如果程序名在主机环境中不可用。如果argc的值为大于1,argv[1]到argv[argc-1]指向的字符串表示程序参数。参数argc和argv以及argv数组指向的字符串应可由程序修改,并在程序之间保留其最后存储的值启动和程序终止。10) 因此,int可以被定义为int的typedef名称替换,或者argv的类型可以写成char**argv等。

C99或C11中的程序终止

main()返回的值以实现定义的方式传输到“环境”。

5.1.2.2.3程序终止1如果主函数的返回类型是与int兼容的类型对主函数的初始调用相当于使用值调用exit函数由主函数返回作为其参数;11) 到达终止main函数返回值0。如果返回类型与int不兼容返回到主机环境的终止状态未指定。11) 根据6.2.4,具有自动存储持续时间的对象的生存期在将在前一种情况下结束,即使在后一种情况中不会结束。

请注意,0被强制要求为“成功”。如果愿意,可以使用<stdlib.h>中的EXIT_FAILURE和EXIT_SUCCESS,但0和1都已建立。另请参阅大于255的退出代码-可能?。

在C89中(因此在Microsoft C中),没有关于main()函数返回但未指定返回值时会发生什么的语句;因此,它导致了未定义的行为。

7.22.4.4退出功能¶5最后,控制返回到主机环境。如果状态值为零或EXIT_SUCCESS,则返回状态成功终止的实现定义形式。如果状态值为EXIT_FAILURE,则返回状态不成功终止的实现定义形式。否则,返回的状态是实现定义的。

标准C++-托管环境

C++11标准(ISO/IEC 14882:2011)规定:

3.6.1主要功能[basic.start.Main]¶1程序应包含名为main的全局函数,该函数是程序的指定开始。[...]¶2实现不应预先定义主功能。该功能不应过载。它应返回类型为int,否则其类型是实现定义的。所有实现应允许以下两种主要定义:int main(){/*…*/}和int main(int argc,char*argv[]){/*…*/}在后一种形式中,argc应为从环境传递给程序的参数数程序在其中运行。如果argc为非零,则应在argv[0]中提供这些参数通过argv[argc-1]作为指向空终止多字节字符串(NTMBS)的初始字符的指针(17.5.1.4.2),argv[0]应是指向表示用于调用程序的名称或“”。argc的值应为非负值。argv的值[argc]应为0。[注意:建议在argv.-end之后添加任何其他(可选)参数注释]¶3不得在程序中使用函数main。main的链接(3.5)由实现定义。[...]¶5 main中的return语句具有离开main函数的效果(使用自动存储持续时间),并以返回值作为参数调用std::exit。如果控制到达终点在没有遇到return语句的情况下,执行返回0;

C++标准明确表示“它(主函数)应该有一个int类型的返回类型,否则它的类型是实现定义的”,并要求支持与C标准相同的两个签名作为选项。因此,C++标准直接不允许“void main()”,尽管它无法阻止允许替代方案的非标准实现。注意,C++禁止用户调用main(但C标准没有)。

C++11标准中的§18.5开始和终止有一段与§7.22.4.4 C11标准中的退出函数(如上所述)相同,除了脚注(仅说明exit_SUCCESS和exit_FAILURE在<cstdlib>中定义)。

标准C-通用扩展

典型地,Unix系统支持第三种变体:

int main(int argc, char **argv, char **envp) { ... }

第三个参数是一个以空结尾的字符串指针列表,每个字符串都是一个环境变量,具有名称、等号和值(可能为空)。如果不使用此选项,仍然可以通过“extern char**environ;”获取环境。这个全局变量在POSIX中是唯一的,因为它没有声明它的头。

C标准认为这是一种常见的扩展,记录在附录J中:

###J.5.1环境论证¶1在托管环境中,主函数接收第三个参数char*envp[],指向指向char的以空结尾的指针数组,每个指针指向一个字符串它提供了有关程序执行环境的信息(5.1.2.2.1)。

Microsoft C

Microsoft VS 2010编译器很有趣。网站上说:

main的声明语法为int main();或者可选地,int main(int argc,char*argv[],char*envp[]);或者,main和wmain函数可以声明为返回void(无返回值)。如果将main或wmain声明为返回void,则不能使用return语句将退出代码返回到父进程或操作系统。要在main或wmain声明为void时返回退出代码,必须使用exit函数。

我不清楚当一个带有void main()的程序退出时会发生什么情况(返回给父系统或操作系统的退出代码),而且MS网站也保持沉默。

有趣的是,MS没有规定C和C++标准所要求的main()的双参数版本。它只规定了一个三参数形式,其中第三个参数是char**envp,一个指向环境变量列表的指针。

Microsoft页面还列出了其他一些替代方法-wmain(),它接受宽字符串,以及其他一些。

此页的Microsoft Visual Studio 2005版本未将void main()列为替代选项。从Microsoft Visual Studio 2008开始的版本可以。

标准C-独立环境

如前所述,上述要求适用于托管环境。如果您使用的是独立环境(这是托管环境的替代方案),那么标准就没什么好说的了。对于独立的环境,在程序启动时调用的函数不必称为main,并且对其返回类型没有限制。标准规定:

5.1.2执行环境定义了两种执行环境:独立环境和托管环境。在这两种情况下,当执行调用指定的C函数时,程序启动环境所有具有静态存储持续时间的对象应在程序启动前初始化(设置为其初始值)。这种初始化的方式和时间未另行规定。程序终止将控制权返回到执行环境。5.1.2.1独立环境在一个独立的环境中(在这种环境中,C程序的执行可能没有任何操作系统的好处),程序启动时调用的函数的名称和类型是由实现定义的。除第4条所要求的最小集合外,任何可供独立程序使用的图书馆设施均由实现定义。独立环境中程序终止的效果由实现定义。

第4条一致性的交叉引用指的是:

¶5严格符合要求的程序只能使用本国际标准中规定的语言和库的功能。3)它不能产生依赖于任何未指定、未定义或实现定义行为的输出,也不能超过任何最低实现限制。¶6两种形式的一致性实施是托管的和独立的。符合要求的托管实施应接受任何严格符合要求的程序。符合要求的独立实施应接受任何严格符合要求的程序,其中库条款(第7条)中规定的功能的使用仅限于标准标题<float.h>、<iso646.h>、<limits.h>、</stdalign.h>的内容,<stdarg.h>、<stdbool.h>、<tddef.h>、<stdint.h>和<stdnorturn.h>。一致的实现可能具有扩展(包括附加的库函数),前提是它们不会改变任何严格符合的程序的行为¶7符合性计划是符合性实施可接受的计划。5)3) 严格符合条件的程序可以使用条件特征(见6.10.8.3),前提是使用相关宏的适当条件包含预处理指令可以保护该使用。例如:#如果定义了__STDC_IEC_559__/*FE_UPWARD*//* ... */fesetround(FE_UPWARD);/* ... */#结束符4) 这意味着,除本国际标准中明确保留的标识符外,一致性实施不保留其他标识符。5) 严格一致的程序旨在在一致的实现中最大限度地可移植。符合性程序可能取决于符合性实现的非可移植特性。

值得注意的是,实际定义任何函数的独立环境所需的唯一标头是<stdarg.h>(甚至那些可能是而且通常只是宏)。

标准C++-独立环境

正如C标准承认托管和独立环境一样,C++标准也承认。(引自ISO/IEC 14882:2011。)

1.4实施合规性[简介合规性]¶7定义了两种实现:托管实现和独立实现。对于托管实现,本国际标准定义了一组可用的库。一个独立的实现是一种可以在没有操作系统的情况下执行的实现,它有一组实现定义的库,其中包括某些语言支持库(17.6.1.3)。¶8一致性实现可能具有扩展(包括附加的库函数),前提是它们不会改变任何格式良好的程序的行为。需要实施来诊断使用根据本国际标准格式不正确的扩展。然而,这样做之后,他们就可以编译和执行这样的程序了。¶9每个实现都应包括文档,以识别其不支持的所有有条件支持的构造,并定义所有特定于语言环境的特征。33) 本文档还定义了实现定义的行为;见1.9。17.6.1.3独立实施[合规]定义了两种实现:托管和独立(1.4)。对于托管实现,本国际标准描述了一组可用的头。独立的实现有一组实现定义的头。该装置应至少包括表16所示的集管。提供的头<cstdlib>版本应至少声明函数abort、atexit、at_quick_exit、exit和quick_eexit(18.5)。此表中列出的其他头应满足托管实现的相同要求。表16-独立实现的C++头子类标题

                                    <ciso646>
18.2  Types                         <cstddef>
18.3  Implementation properties     <cfloat> <limits> <climits>
18.4  Integer types                 <cstdint>
18.5  Start and termination         <cstdlib>
18.6  Dynamic memory management     <new>
18.7  Type identification           <typeinfo>
18.8  Exception handling            <exception>
18.9  Initializer lists             <initializer_list>
18.10 Other runtime support         <cstdalign> <cstdarg> <cstdbool>
20.9  Type traits                   <type_traits>
29    Atomics                       <atomic>

在C中使用int main()怎么样?

C11标准的标准§5.1.2.2.1显示了首选符号int main(void),但标准中也有两个示例显示int main():§6.5.3.4¶8和§6.7.6.3¶20。现在,需要注意的是,示例不是“规范性的”;它们只是说明性的。如果示例中存在错误,它们不会直接影响标准的主要文本。也就是说,它们强烈地表明了预期的行为,因此如果标准在一个示例中包含int main(),则表明int main(()不是禁用的,即使它不是首选的表示法。

6.5.3.4 sizeof和_Aligef运算符…¶8示例3在本示例中,计算可变长度数组的大小并从函数返回:#包括<stddef.h>size_t fsize3(整数n){字符b[n+3];//可变长度阵列返回sizeof b;//执行时间大小}int main(){size_t大小;size=fsize3(10);//fsize3返回13返回0;}

像int main(){…}这样的函数定义确实指定函数不接受参数,但不提供函数原型AFAICT。对于main(),这很少是问题;但这确实意味着,如果对main()进行递归调用,则不会检查参数。对于其他函数,这更是一个问题——当调用函数时,您确实需要在作用域中使用原型,以确保参数正确。

您通常不会在IOCCC之类的地方之外递归调用main(),并且在C++中明确禁止您这样做。我确实有一个测试程序,主要是为了新奇。如果您有:

int i = 0;
int main()
{
    if (i++ < 10)
        main(i, i * i);
    return 0;
}

并且使用GCC编译,不包括-Wstrict原型,它在严格的警告下干净地编译。如果它是main(void),则无法编译,因为函数定义显示“无参数”。

省略返回0

当C或C++程序到达main结尾时,编译器将自动生成返回0的代码,因此不需要将返回值设置为0;明确地在main的末尾。

注意:当我提出这个建议时,后面几乎总是两种评论中的一种:“我不知道。”或“这是坏建议!”我的理由是,依赖标准明确支持的编译器行为是安全和有用的。对于C,自C99以来;见ISO/IEC 9899:1999第5.1.2.2.3节:

[…]从初始调用返回到主函数相当于用主函数返回的值作为其参数调用退出函数;到达终止主函数的}返回值0。

对于C++,自1998年第一个标准以来;见ISO/IEC 14882:1998第3.6.1节:

如果控件到达main的结尾而没有遇到return语句,则效果是执行return 0;

从那时起,两种标准的所有版本(C99和C++98)都保持了相同的想法。我们依赖于C++中自动生成的成员函数,很少有人编写显式返回;void函数末尾的语句。反对省略的理由似乎归结为“看起来很奇怪”。如果像我一样,你对C标准更改的原因感到好奇,请阅读这个问题。还要注意,在20世纪90年代初,这被认为是“草率的做法”,因为当时这是一种未定义的行为(尽管得到广泛支持)。

此外,C++核心指南包含省略返回0的多个实例;在main结尾,没有编写显式返回的实例。尽管该文件中还没有关于这一特定主题的具体准则,但这似乎至少是对这一做法的默认认可。

所以我主张省略它;其他人不同意(通常是激烈的!)在任何情况下,如果你遇到省略了它的代码,你会知道它是由标准明确支持的,你也会知道它的含义。

这里有一个关于返回代码用法的小演示。。。

当使用Linux终端提供的各种工具时,可以使用返回代码,例如在过程完成后进行错误处理。假设存在以下文本文件myfile:

这是一些示例,以检查grep的工作原理。

执行grep命令时,将创建一个进程。一旦它通过(并且没有中断),它将返回一些介于0和255之间的代码。例如:

$ grep order myfile

如果你这样做

$ echo $?
$ 0

您将获得0。为什么?因为grep找到了匹配项并返回了退出代码0,这是成功退出的通常值。原因可能在于简单检查是否一切正常的布尔性质。对0(布尔值假)的简单否定返回1(布尔值真),这可以在if-else语句中轻松处理。

让我们再次检查它,但不在文本文件中,因此不会找到匹配项:

$ grep foo myfile
$ echo $?
$ 1

由于grep未能将标记“foo”与文件内容匹配,因此返回代码为1(这是发生故障时的常见情况,但如上所述,您有很多值可供选择)。同样,如果我们将其放在简单的布尔上下文中(一切正常与否),则否定1(布尔值为真)会产生0(布尔值假),这同样可以通过if-else语句轻松处理。当涉及布尔值时,任何不是0的值都被视为等同于1(因此,在用于检查是否发生错误的简单if-else语句中,2、3、4等的工作方式与使用1的情况相同)。您可以使用不同的返回值来增加错误状态的粒度。对于成功执行的状态,使用0以外的任何值都被认为是不好的做法(由于上面给出的原因)。

以下bash脚本(只需在Linux终端中键入它)虽然非常基本,但应该能给出一些错误处理的概念:

$ grep foo myfile
$ CHECK=$?
$ [ $CHECK -eq 0] && echo 'Match found'; [ $CHECK -ne 0] && echo 'No match was found'
$ No match was found

在第二行之后,由于“foo”使grep返回1,所以没有任何内容打印到终端,我们检查grep的返回代码是否等于0。第二个条件语句在最后一行回显其消息,因为由于CHECK==1,它是真的。

正如您所看到的,如果您正在调用这个和那个进程,有时需要查看它返回了什么(通过main()的返回值),例如在运行测试时。

ISO现在要求C和C++都使用“int”作为“main”的返回类型。

这两种语言以前都允许隐式“int”,而“main”的声明没有任何返回类型。事实上,C++的第一个外部版本(1985年2月发布的“cfront”E版),用自己的语言编写,声明为“main”,没有任何返回类型。。。但返回了一个整数值:错误数或127,以较小者为准

至于返回什么的问题:C和C++的ISO标准与POSIX标准同步工作。对于符合POSIX标准的任何托管环境,(1) 126为OS的外壳保留以指示不可执行的实用程序,(2) 127为OS的外壳保留,(3) 公用设施的退出值在逐个公用设施的基础上单独列出,(4) 调用外壳外部的实用程序的程序应该为它们自己的出口使用类似的值,(5) 值128和以上用于指示由于接收信号而导致的终止,(6) 值1-125表示故障,(7) 值0表示成功。

在C和C++中,值EXIT_SUCCESS和EXIT_FAILURE用于处理最常见的情况:用于报告成功或仅报告一般失败的程序。它们可以但不必分别等于0和1。

这意味着,如果您希望程序为不同的故障模式或状态指示返回不同的值,同时继续使用这两个常量,您可能必须首先确保附加的“故障”或“状态”值严格位于max(EXIT_SUCCESS,EXIT_failure)和126之间(并希望两者之间有足够的空间),并保留EXIT_FAILURE以标记通用或默认故障模式。

否则,如果你不打算使用常量,那么你应该按照POSIX的要求去做。

对于在独立环境或不符合POSIX的主机上使用的程序,我只能说以下几点:

我已经编写了独立的程序——作为自定义运行时系统上的多线程程序(以及其他一切的自定义工具库)。我遵循的一般规则是:(1) “main”运行前台进程,通常仅由启动、配置或初始化例程组成,但也可能包含用于连续操作的前台进程(如轮询循环),(2) “main”返回到无限睡眠和等待循环,(3) 未定义或使用“main”的返回值,(4) 后台进程单独运行,作为中断驱动和事件驱动线程,独立于“主”,仅在收到重置信号或其他线程时终止。。。或者简单地关闭对驱动线程的任何事件的监视。

注意,C和C++标准定义了两种实现:独立的和托管的。

C90托管环境

允许的表单1:

int main (void)
int main (int argc, char *argv[])

main (void)
main (int argc, char *argv[])
/*... etc, similar forms with implicit int */

评论:

前两个被显式声明为允许的形式,其他的被隐式允许,因为C90允许返回类型和函数参数为“隐式int”。不允许使用其他形式。

C90独立环境

允许使用任何形式或名称的main 2。

C99托管环境

允许的表单3:

int main (void)
int main (int argc, char *argv[])
/* or in some other implementation-defined manner. */

评论:

C99删除了“隐式int”,因此main()不再有效。

引入了一个奇怪的、模棱两可的句子“或以某种其他实现定义的方式”。这可以被解释为“intmain()的参数可能不同”或“main可以有任何实现定义的形式”。

一些编译器选择以后一种方式解释标准。可以说,人们不能轻易地通过引用标准本身来说明他们不符合标准,因为它是模棱两可的。

然而,允许main()的完全野生形式可能不是(?)这个新句子的意图。C99的基本原理(非规范性)意味着该语句引用了int main 4的附加参数。

然而,托管环境程序终止部分继续讨论main不返回int 5的情况。尽管该部分对于如何声明main并不是规范性的,但它肯定意味着即使在托管系统上,main也可以以完全实现定义的方式声明。

C99独立环境

允许使用任何形式或名称的main。

C11托管环境

允许的表单7:

int main (void)
int main (int argc, char *argv[])
/* or in some other implementation-defined manner. */

C11独立环境

允许使用任何形式或名称的main。


注意,在上述任何版本中,int main()从未被列为C的任何托管实现的有效形式。在C中,与C++不同,()和(void)有不同的含义。前者是一种过时的特征,可以从语言中删除。参见C11未来语言方向:

6.11.6函数声明器使用带有空括号的函数声明符(而不是原型格式参数类型声明符)是一种过时的特性。


C++03托管环境

允许的表单9:

int main ()
int main (int argc, char *argv[])

评论:

注意第一种形式中的空括号。在这种情况下,C++和C是不同的,因为在C++中,这意味着函数不带参数。但在C中,这意味着它可以采用任何参数。

C++03独立环境

启动时调用的函数的名称是实现定义的。如果它被命名为main(),它必须遵循所述的格式10:

// implementation-defined name, or 
int main ()
int main (int argc, char *argv[])

C++11托管环境

允许的表单11:

int main ()
int main (int argc, char *argv[])

评论:

标准文本已更改,但含义相同。

C++11独立环境

启动时调用的函数的名称是实现定义的。如果它被命名为main(),它必须遵循所述的形式12:

// implementation-defined name, or 
int main ()
int main (int argc, char *argv[])

工具书类

ANSI X3.159-1989 2.1.2.2托管环境。“程序启动”

程序启动时调用的函数名为main。这个实现没有声明此函数的原型。应为定义为返回类型为int且没有参数:

int main(void) { /* ... */ } 

或具有两个参数(这里称为argc和argv,但可以使用任何名称,因为它们是本地的声明它们的函数):

int main(int argc, char *argv[]) { /* ... */ }

ANSI X3.159-1989 2.1.2.1独立环境:

在独立的环境中(C程序执行可能需要没有任何操作系统好处的地方)、名称和类型在程序启动时调用的函数的实现是定义的。

ISO 9899:1999 5.1.2.2托管环境->5.1.2.2.1程序启动

程序启动时调用的函数名为main。这个实现没有声明此函数的原型。应为定义为返回类型为int且没有参数:

int main(void) { /* ... */ } 

或具有两个参数(这里称为argc和argv,但可以使用任何名称,因为它们是本地的声明它们的函数):

int main(int argc, char *argv[]) { /* ... */ }

或同等产品;9) 或在定义的某些其他实现中方式

国际标准编程语言-C的基本原理,修订版5.10。5.1.2.2托管环境-->5.1.2.2.1程序启动

main参数的行为,以及exit、main和atexit的交互作用(见§7.20.4.2)已被编入法典,以抑制argv表示中的一些不必要的变化字符串,以及main返回值的含义。

argc和argv作为main参数的规范承认了广泛的先前实践。argv[argc]必须是空指针,以便为列表末尾提供冗余检查,这也是基于常见的做法。

main是唯一一个可以用零或两个参数声明的函数。(其他函数的参数数量必须在调用和定义之间完全匹配。)这种特殊情况只是认识到当程序不访问程序参数字符串时,将参数保留为main的普遍做法。尽管许多实现支持两个以上的主要论点,但标准既不支持也不禁止这种做法;用三个参数定义main的程序并不严格符合(见§J.5.1.)。

ISO 9899:1999 5.1.2.2托管环境-->5.1.2.2.3程序终止

如果主函数的返回类型是与int兼容的类型,那么从初始调用到主函数的一个返回就相当于用主函数返回的值作为其参数调用退出函数;11) 到达终止主函数的}返回值0。如果返回类型与int不兼容,则返回到主机环境的终止状态未指定。

ISO 9899:1999 5.1.2.1独立环境

在一个独立的环境中(在这种环境中,C程序的执行可能没有任何操作系统的好处),程序启动时调用的函数的名称和类型是由实现定义的。

ISO 9899:2011 5.1.2.2托管环境->5.1.2.2.1程序启动

本节与上述C99节相同。

ISO 9899:1999 5.1.2.1独立环境

本节与上述C99节相同。

ISO 14882:2003 3.6.1主要功能

实现不应预先定义主功能。该功能不应过载。它应该具有int类型的返回类型,否则它的类型是实现定义的。所有实现应允许以下两种主要定义:

int main() { /* ... */ }

int main(int argc, char* argv[]) { /* ... */ }

ISO 14882:2003 3.6.1主要功能

它定义了独立环境中的程序是否需要定义主功能。

ISO 14882:2011 3.6.1主要功能

实现不应预先定义主功能。该功能不应过载。它应该具有int类型的返回类型,否则它的类型是实现定义的。所有实施应允许两者-返回int和-返回int的(int,指向指向char的指针)函数作为主要类型(8.3.5)。

ISO 14882:2011 3.6.1主要功能

本节与上述C++03节相同。