在C和C++中,main()函数应该返回什么?

840
在C和C++中,定义main()函数的正确(最有效)方式是什么 - int main()还是void main() - 以及为什么?参数又是怎样的呢? 如果选择int main(),那么是return 1还是return 0

38
我仍然认为这个问题也相当模糊。请为我定义“最有效率”。效率是指哪方面?是指占用更少的内存吗?还是指运行速度更快?虽然我可以看到一些有用的答案,但我仍然认为这个问题的措辞非常糟糕。 - Onorio Catenacci
12
这里"efficient"的上下文很明显,尤其是通过例子(很可能是为了澄清“efficient”的定义)也更易于理解。希望可怜的缓冲区没有退缩并且完全后悔提出这个问题。无论返回void还是int,都会返回一个值,因此不会影响文件大小、执行操作或分配内存。在大多数操作系统中,人们倾向于在成功时返回0,在-其他-成功或失败时返回其他内容,但没有标准。最终,在任何明显方式上都没有效率上的差异。 - Kit10
“正确(最有效)”没有意义。高效是一回事,正确是另一回事。main只被调用一次(在C++中只能被调用一次:没有递归)。如果您不希望执行在main中花费大量时间,则不要多次调用程序:使程序实现重复。 - Kaz
3
我很感兴趣,目前为止我所看到的答案中似乎没有提供完整可运行的例子,包括#include语句。 - puk
7
在没有操作系统的平台上,返回值毫无意义。你没有返回到任何地方。如果在嵌入式设备的 main(...) 函数里使用了 return 语句,你的系统将进入不可预测状态,你的洗衣机可能会变得自我意识并试图杀死你。因此,在这种情况下我们使用 void main()。这是裸机嵌入式的行业标准做法。 - 3Dave
显示剩余5条评论
19个回答

658

main函数的返回值表示程序如何退出。从main正常退出的情况下,返回值为0。非正常退出时,返回一个非零值,但没有标准规定如何解释非零返回值。正如其他人指出的那样,C++标准禁止使用void main(),应该避免使用。有效的C++main函数签名包括:

int main(void)

int main(int argc, char **argv)

等价于

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

值得注意的是,在C++中,int main()可以没有返回语句,此时默认返回0。在C99程序中也是如此。是否省略return 0;存在争议。有效的C程序main函数签名范围更广。

main函数的效率不是问题。根据C++标准,它只能被输入和离开一次(标志着程序的开始和终止)。对于C语言,允许重新进入main(),但应避免这样做。


86
主程序可以被多次进入或离开,但这样的程序可能不会赢得任何设计奖项 ;) - korona
20
C99也有C++中的瑕疵,即在main()函数结尾处达到等同于返回0的效果——如果main()被定义为返回与int兼容的类型(5.1.2.2.3节)。 - Jonathan Leffler
73
重新进入 main 不是有效的 C++。标准明确规定,3.6.1.3 声明“main 不应在程序内使用”。 - workmad3
139
为此目的,stdlib.h 提供了 EXIT_SUCCESS 和 EXIT_FAILURE。 - Clay
31
0和非零对于阅读您的代码的人来说是正确的,但完全没有意义。这个问题证明了人们不知道什么是有效/无效的代码。EXIT_SUCCESS / EXIT_FAILURE更加清晰易懂。 - JaredPar
显示剩余27条评论

218

看起来已经有一个针对C++的接受答案,因此我想添加一个关于C的答案,这与之有一些不同。在ISO/IEC 9899:1989(C90)和ISO/IEC 9899:1999(C99)之间也进行了一些更改。

main()应该声明为以下两种形式之一:

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

或等效的。例如,int main(int argc, char *argv[]) 等同于第二种写法。在 C90 中,可以省略 int 返回类型,因为它是默认值,但在 C99 及更新版本中,int 返回类型不能省略。

如果一个实现允许,main() 可以用其他方式声明(例如,int main(int argc, char *argv[], char *envp[])),但这会使程序实现变得不再严格符合标准。

标准定义了 3 个返回值是严格符合标准(即不依赖于实现定义行为)的值:0EXIT_SUCCESS 表示成功终止,EXIT_FAILURE 表示失败终止。任何其他的返回值都是非标准和实现定义的。在 C90 中,main() 必须在结尾处有一个显式的 return 语句来避免未定义的行为。在 C99 和更新版本中,可以省略 main() 的返回语句。如果你这样做了,当 main() 结束时,会有一个隐含的 return 0

最后,从标准的角度来看,从 C 程序中递归调用 main() 是没有问题的。


1
@Lundin 我认为你不需要引用来表明某人可以制作一个接受非标准符合程序的编译器,或者拥有一个非标准符合的编译器。这是常识和常理。 - KABoissonneault
5
“实现定义行为”是标准中的术语,与完全未记录的行为不同。如果您实现了标记为“实现定义行为”的内容,则仍遵循标准。在这种情况下,引用了C89标准,因为它没有列出任何此类“实现定义行为”,因此需要引用来证明他并非凭空捏造。 - Lundin
1
@Lundin,你的看法有误。我们讨论的不是实现定义行为,而是如果实现选择这样做,则实现偏离标准。 这更像是孩子不听父母的话:你不需要引用父母的话告诉你孩子可以以何种方式违背父母的话。你只需要知道,一旦孩子选择这样做,他们就不再遵守父母的指导方针。 - KABoissonneault
2
@KABoissonneault 我在评论中引用的部分绝对是关于实现定义行为的(而不是非标准编译器扩展)。因此,我正在谈论实现定义行为。如果你在独白其他事情,祝你好运。 - Lundin
2
@Lundin 我猜引用中的措辞有些令人困惑(其中提到“但这使得程序实现被定义”),但我非常确定该人在谈论非标准行为(如“如果实现允许”和“不再严格符合[标准]”),而不是实际的实现定义行为。 该人肯定应该重新措辞他们的答案,但我仍然认为不需要引用标准上的内容。 - KABoissonneault
显示剩余9条评论

146

标准C — 托管环境

对于托管环境(即正常情况下),C11标准(ISO/IEC 9899:2011)规定:

5.1.2.2.1 程序启动
程序启动时调用的函数名为`main`。该函数的实现不声明原型。它应该定义为返回类型为`int`且没有参数的形式:
```c int main(void) { /* ... */ } ```
或者带有两个参数(在此称为`argc`和`argv`,但可以使用任何名称,因为它们是在声明它们的函数内部局部的):
```c int main(int argc, char *argv[]) { /* ... */ } ```
或者等效的方式;或以其他一些实现定义的方式。
如果声明了参数,则`main`函数的参数应满足以下约束条件:
- `argc`的值必须是非负数。 - `argv[argc]`必须是空指针。 - 如果`argc`的值大于零,则数组成员`argv[0]`到`argv[argc-1]`(包括)必须包含指向字符串的指针,这些指针在程序启动之前由主机环境赋予实现定义的值。其目的是为了在程序启动之前从托管环境中的其他地方提供给程序确定的信息。如果主机环境无法提供同时包含大写和小写字母的字符串,则实现必须确保字符串以小写形式接收。 - 如果`argc`的值大于零,则`argv[0]`指向的字符串表示程序名称;如果主机环境无法提供程序名称,则`argv[0][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 如果main函数的返回类型与int兼容,那么从初始调用main函数返回等同于使用main函数的返回值作为参数调用exit函数;到达终止main函数的}将返回值0。如果返回类型与int不兼容,则返回给主机环境的终止状态是未指定的。

注:根据6.2.4的规定,在前一种情况下,具有自动存储期限的对象在main中声明后其生命周期已结束,即使在后一种情况下也是如此。

请注意,将0指定为“成功”。如果您愿意,您可以使用<stdlib.h>中的EXIT_FAILUREEXIT_SUCCESS,但是0已经被广泛接受,1也是如此。另请参阅Exit codes greater than 255 — possible?
在C89(因此也适用于Microsoft C)中,没有关于main()函数返回但未指定返回值时会发生什么的说明;因此,这将导致未定义的行为。

7.22.4.4 exit函数

¶5 最后,控制权返回给主机环境。如果status的值为零或EXIT_SUCCESS,则返回一个实现定义的成功终止状态形式。如果status的值为EXIT_FAILURE,则返回一个实现定义的失败终止状态形式。否则,返回的状态是实现定义的。

标准C++ — 托管环境

C++11标准(ISO/IEC 14882:2011)指出:

3.6.1 Main function [basic.start.main]

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

¶2 An implementation shall not predefine the main function. This function shall not be overloaded. It shall have a return type of type int, but otherwise its type is implementation defined. All implementations shall allow both of the following definitions of main:

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

and

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

In the latter form argc shall be the number of arguments passed to the program from the environment in which the program is run. If argc is nonzero these arguments shall be supplied in argv[0] through argv[argc-1] as pointers to the initial characters of null-terminated multibyte strings (NTMBSs) (17.5.2.1.4.2) and argv[0] shall be the pointer to the initial character of a NTMBS that represents the name used to invoke the program or "". The value of argc shall be non-negative. The value of argv[argc] shall be 0. [Note: It is recommended that any further (optional) parameters be added after argv. —end note]

¶3 The function main shall not be used within a program. The linkage (3.5) of main is implementation-defined. [...]

¶5 A return statement in main has the effect of leaving the main function (destroying any objects with automatic storage duration) and calling std::exit with the return value as the argument. If control reaches the end of main without encountering a return statement, the effect is that of executing

return 0;

C++标准明确表示:“主函数应具有类型为int的返回类型,但其它部分的类型由实现定义”,并要求支持与C标准相同的两种签名作为选项。因此,《C++标准》直接不允许使用'void main()',尽管它无法阻止非标准实现允许使用其他替代方式。请注意,《C++标准》禁止用户调用main(但C标准允许)。

在C++11标准的§18.5 启动和终止一段与C11标准的§7.22.4.4 exit函数的一段完全相同(如上所引),除了一个脚注(仅记录了在<cstdlib>中定义了EXIT_SUCCESSEXIT_FAILURE)。

标准C — 共同扩展

传统上,Unix系统支持第三种变体:

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

第三个参数是一个以空字符结尾的字符串指针列表,每个字符串都是一个环境变量,包含一个名称、一个等号和一个值(可能为空)。如果您不使用它,仍然可以通过'extern char **environ;'访问环境变量。这个全局变量在POSIX中是唯一的,因为它没有声明它的头文件。
C标准将其识别为一种常见的扩展,详细说明在附录J:
### J.5.1 环境参数 ¶1 在主机环境中,main函数接收第三个参数'char *envp[]',它指向一个以空字符结尾的指针数组,每个指针指向一个提供关于程序执行环境信息的字符串(5.1.2.2.1)。

Microsoft C

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

The declaration syntax for main is

 int main();

or, optionally,

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

Alternatively, the main and wmain functions can be declared as returning void (no return value). If you declare main or wmain as returning void, you cannot return an exit code to the parent process or operating system by using a return statement. To return an exit code when main or wmain is declared as void, you must use the exit function.

对我来说,当一个带有void main()的程序退出时(返回给父进程或操作系统的退出代码)是不清楚的,而微软网站也没有提供相关信息。

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

微软页面还列出了一些其他替代方案-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 A strictly conforming program shall use only those features of the language and library specified in this International Standard.3) It shall not produce output dependent on any unspecified, undefined, or implementation-defined behavior, and shall not exceed any minimum implementation limit.

¶6 The two forms of conforming implementation are hosted and freestanding. A conforming hosted implementation shall accept any strictly conforming program. A conforming freestanding implementation shall accept any strictly conforming program in which the use of the features specified in the library clause (clause 7) is confined to the contents of the standard headers <float.h>, <iso646.h>, <limits.h>, <stdalign.h>, <stdarg.h>, <stdbool.h>, <stddef.h>, <stdint.h>, and <stdnoreturn.h>. A conforming implementation may have extensions (including additional library functions), provided they do not alter the behavior of any strictly conforming program.4)

¶7 A conforming program is one that is acceptable to a conforming implementation.5)

3) A strictly conforming program can use conditional features (see 6.10.8.3) provided the use is guarded by an appropriate conditional inclusion preprocessing directive using the related macro. For example:

#ifdef __STDC_IEC_559__ /* FE_UPWARD defined */
    /* ... */
    fesetround(FE_UPWARD);
    /* ... */
#endif

4) This implies that a conforming implementation reserves no identifiers other than those explicitly reserved in this International Standard.

5) Strictly conforming programs are intended to be maximally portable among conforming implementations. Conforming programs may depend upon non-portable features of a conforming implementation.

值得注意的是,唯一需要在独立环境中定义任何函数的头文件是<stdarg.h>(甚至这些也可能是 - 通常是 - 宏)。
标准C++ - 独立环境
正如C标准承认了托管和独立环境一样,C++标准也是如此。(引自ISO/IEC 14882:2011。)
1.4 实施合规性 [intro.compliance]
¶7 定义了两种实施方式:托管实施和独立实施。对于托管实施,本国际标准定义了可用库的集合。独立实施是指在没有操作系统的情况下进行执行,并具有一组由实施定义的库,其中包括某些语言支持库(17.6.1.3)。
¶8 符合要求的实施可以具有扩展功能(包括额外的库函数),只要它们不改变任何格式正确程序的行为。实施必须诊断使用此类扩展的程序,如果这些程序根据本国际标准是格式不正确的。然而,在完成诊断后,它们可以编译和执行这些程序。
¶9 每个实施都应包含文档,以识别其不支持的所有有条件支持的结构,并定义所有特定于区域设置的特征。3 3) 此文档还定义了实施定义的行为;请参见1.9。
17.6.1.3 独立实施 [compliance]
定义了两种实施方式:托管和独立(1.4)。对于托管实施,本国际标准描述了可用头文件的集合。
独立实施具有一组由实施定义的头文件。此集合至少应包括表16中显示的头文件。
提供的头文件<cstdlib>应至少声明函数abortatexitat_quick_exitexitquick_exit(18.5)。此表中列出的其他头文件应满足与托管实施相同的要求。

表16 — 用于独立实现的C++头文件

子句 头文件
<ciso646>
18.2 类型 <cstddef>
18.3 实现属性 <cfloat> <limits> <climits>
18.4 整数类型 <cstdint>
18.5 启动和终止 <cstdlib>
18.6 动态内存管理 <new>
18.7 类型识别 <typeinfo>
18.8 异常处理 <exception>
18.9 初始化列表 <initializer_list>
18.10 其他运行时支持 <cstdalign> <cstdarg> <cstdbool>
20.9 类型特性 <type_traits>
29 原子操作 <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 The sizeof and _Alignof operators

¶8 EXAMPLE 3 In this example, the size of a variable length array is computed and returned from a function:

#include <stddef.h>

size_t fsize3(int n)
{
    char b[n+3]; // variable length array
    return sizeof b; // execution time sizeof
}
int main()
{
    size_t size;
    size = fsize3(10); // fsize3 returns 13
    return 0;
}
int main(){ … }这样的函数定义确实指定了该函数不接受任何参数,但并没有提供函数原型,据我所知。对于main()来说,这很少是个问题;但这意味着如果你对main()进行递归调用,参数将不会被检查。对于其他函数来说,这就成为了一个更大的问题——在调用函数时,你确实需要一个作用域内的原型来确保参数正确。
通常情况下,你不会在除了IOCCC之类的地方递归调用main(),而且在C++中明确禁止这样做。我确实有一个测试程序可以这样做——主要是为了新奇。如果你有:
int i = 0;
int main()
{
    if (i++ < 10)
        main(i, i * i);
    return 0;
}

使用GCC编译时,不要包含-Wstrict-prototypes选项,代码将在严格警告下编译通过。如果函数定义为main(void),则无法编译,因为函数定义中指定了“无参数”。

1
@DavidBowling:像 int main(){ … } 这样的函数定义确实指定了该函数不需要参数,但是未提供函数原型(Function prototype),据我所知。对于 main() 来说,这很少是一个问题;这意味着如果您对 main() 进行递归调用,则不会检查参数。对于其他函数来说,这更成为一个问题——当调用函数时,确实需要一个在作用域内的原型,以确保参数正确。 - Jonathan Leffler
1
@DavidBowling:通常不会在除了IOCCC之类的地方递归调用main()。我有一个测试程序可以做到这一点——主要是为了新奇性。如果你有int i = 0; int main() { if (i++ < 10) main(i, i * i); return 0; }并使用GCC编译而没有包含-Wstrict-prototypes,它将在严格的警告下编译通过。如果是main(void),它将无法编译。 - Jonathan Leffler
我正在阅读Dennis Ritchie的《C程序设计语言》,尽管他的main()函数有返回值,但他从未在main()之前加上int。你知道为什么吗?似乎这里的每个人都说应该写成int main(),但是C语言的创造者在他的ANSI C书中并没有这样写。 - Joe
1
因为即使是《C程序设计语言》第二版也早于第一个标准C(我有一本封面右上角印有“基于草案建议的ANSI C”字样的副本)。在C90中,如果返回类型为int,则不必为函数包括返回类型。如果在没有先前声明的情况下使用函数,则假定其返回int。但是,C90标准不是当前标准。当前标准是C18,取代了C11和C99。———_[...继续...]_ - Jonathan Leffler
1
我建议阅读King的《C程序设计:现代方法》或Gustedt的《现代C语言》——请参考《The Definitive C Book Guide and List》,这个标题比问答内容更为宏大。 - Jonathan Leffler
显示剩余3条评论

67

我认为 main() 函数应该返回 EXIT_SUCCESSEXIT_FAILURE。它们在 stdlib.h 中被定义。


4
EXIT_SUCCESSEXIT_FAILURE之所以存在,是因为一些历史操作系统(比如VMS?)使用不同于0的数字来表示成功。但是现在在任何地方都是0。 - fuz
8
你说得没错,但这并不与我的评论相冲突。EXIT_SUCCESS确实可以是非零值,但标准(C89、C99、C11)都定义了0(以及EXIT_SUCCESS)也是成功终止状态的一种实现定义形式。 - Chris Young
2
@FUZxxl:VMS确实使用奇数值(如1)表示成功,偶数值(如0)表示失败。不幸的是,最初的ANSI C标准被解释为EXIT_SUCCESS必须为0,因此从main返回EXIT_SUCCESS在VMS上得到了完全错误的行为。在VMS上可移植的做法是使用exit(EXIT_SUCCESS),这总是做正确的事情。 - Adrian McCarthy
1
如果主函数的返回类型与int兼容,则从主函数的初始调用返回等同于使用主函数返回的值作为其参数调用exit函数;到达终止主函数的}会返回值0。 - Lundin
1
然后是关于退出函数的7.22.4.4部分:“如果状态值为零或EXIT_SUCCESS,则返回一个实现定义的成功终止状态形式。如果状态值为EXIT_FAILURE,则返回一个实现定义的不成功终止状态形式。否则,返回的状态是实现定义的。” - Lundin
显示剩余2条评论

50

请注意,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允许函数返回类型和函数参数使用“implicit int”。不允许使用其他格式。

  • C90独立环境

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

  • C99托管环境

允许使用以下格式3

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

注释:

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

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

一些编译器选择以后者的方式解释标准。可以说,通过引用标准本身来证明它们不符合规范并不容易,因为它是模糊的。

然而,允许完全狂野的main()形式可能(?)不是这个新句子的意图。 C99的理由(非规范性的)暗示该句子指的是int main的附加参数4

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

  • C99自由环境

允许任何形式或名称的main 6

  • C11主机环境

允许的形式7

int main (void)
int main (int argc, char *argv[])
/* or in some other implementation-defined manner. */
  • C11自由环境

允许任何形式或名称的主函数8


请注意,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[])

参考资料

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

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

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

或者使用两个参数(这里称为 argc 和 argv,但可以使用任何名称,因为它们是在声明它们的函数中局部的):

int main(int argc, char *argv[]) { /* ... */ }
  1. ANSI X3.159-1989 2.1.2.1 自由环境:

在自由环境中(在此环境下,C程序可以在没有操作系统的情况下执行),调用程序启动时的函数的名称和类型是实现定义的。

  1. 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,但这种做法既没有得到认可,也没有被禁止。定义具有三个参数的main的程序不是严格符合要求的(参见§J.5.1.)。

  1. ISO 9899:1999 5.1.2.2 主机环境 -> 5.1.2.2.3 程序终止

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

  1. ISO 9899:1999 5.1.2.1 自由环境

在自由环境中(其中C程序执行可以在没有操作系统任何好处的情况下进行),在程序启动时调用的函数的名称和类型是实现定义的。

  1. ISO 9899:2011 5.1.2.2 Hosted environment -> 5.1.2.2.1 Program startup

这一节与引用的C99版节完全相同。

  1. ISO 9899:1999 5.1.2.1 自由环境

此部分与引用的C99版节完全相同。

  1. ISO 14882:2003 3.6.1 Main函数

一个实现不得预定义main函数。这个函数不能被重载。它应该有一个类型为int的返回类型,但是它的类型是由实现定义的。所有的实现都应该允许以下两种main函数的定义:

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

并且

int main(int argc, char* argv[]) { /* ... */ }
  1. ISO 14882:2003 3.6.1 主函数

在自由环境下,程序是否需要定义主函数是由实现定义的。

  1. ISO 14882:2011 3.6.1 主函数

实现不能预定义主函数。此函数不得重载。它应该具有int类型的返回类型,但其类型除此之外是由实现定义的。 所有实现都应允许使用以下两种类型之一作为main(8.3.5)类型:

— 返回int的()函数和

— 返回int的(int、char指针的指针)函数。

  1. ISO 14882:2011 3.6.1 主函数

本节与上面引用的C++03版本相同。


一个问题:C++标准是否意味着在独立环境中启动函数的签名也是实现定义的?例如,实现可以将启动函数定义为:int my_startup_function()int my_startup_function(int argc, char *argv[]),但它可以将char my_startup_function(long argc, int *argv[])作为启动函数吗?我想不行,对吧?而且,这不是也有歧义吗? - Utku
@Utku 它可以有任何签名,只要它不被命名为 main(),因为那么它必须使用列出的签名之一。我想最常见的签名应该是 void my_startup_function(),因为在自由站系统上从程序返回是没有意义的。 - Lundin
1
我明白了。但是如果允许为启动函数使用任何名称和任何签名,为什么不允许为“main”使用不同的签名呢?如果这个问题不太聪明,请原谅,因为我无法理解背后的推理。 - Utku
@Utku C和C++是不同的。至于为什么C++要强制这样做,我不知道,没有理由。我怀疑主要罪魁祸首(双关语)是Stroustrup,他早期宣布main必须返回int,没有例外。因为当他制作第一个C++版本时,他只习惯了托管系统。在链接的帖子中,Stroustrup仍然对自由实现的存在毫不知情:例如,他无知地提到了C标准的托管实现子章节,忽略了5.1.2.1章节的存在。 - Lundin
1
C11标准草案的一个值得注意的事情是,即使func()被认为是过时的,草案本身在其自己的示例中使用int main() - Antti Haapala -- Слава Україні
显示剩余3条评论

32

成功返回0,错误返回非零值。这是UNIX和DOS脚本使用的标准,以了解程序发生了什么。


11

main()在C89和K&R C中未指定返回类型,默认为“int”。

return 1? return 0?
  1. 如果你在 int main() 中没有编写返回语句,那么默认情况下,闭合的 } 会返回 0(仅适用于 C++ 和 C99 及其更新版本,在 C90 中必须编写返回语句。详见Why main does not return 0 here?)。
  1. return 0return 1 将由父进程接收。在 shell 中,它将进入一个 shell 变量中,如果您正在从 shell 运行程序而不使用该变量,则无需担心 main() 的返回值。

详见How can I get what my main function has returned?

$ ./a.out
$ echo $?

通过这种方式,您可以看到变量$?接收了main()的返回值的最低有效字节。

在Unix和DOS脚本中,通常成功时返回0,出错时返回非零值。这是Unix和DOS脚本使用的标准,用于查找程序发生了什么并控制整个流程。


4
严格来说,"$?"不是一个环境变量,而是一个shell预定义的(或内置的)变量。区别很难发现,但如果你运行env(不带任何参数),它会打印出环境,并且"$?"不会在环境中显示。 - Jonathan Leffler
1
当主函数“掉到末尾”时,只有在C++和C99及以后的版本中才会自动返回0,而在C90中则不会。 - Kaz
@Kaz 是的,我已经相应地更新了答案。实际上,我曾经问过这个问题 https://dev59.com/Omoy5IYBdhLWcg3wPbyd - Jeegar Patel

8

请记住,尽管您返回的是int类型,但某些操作系统(如Windows)会将返回值截断为一个字节(0-255)。


5
Unix和大多数其他操作系统可能都会执行相同的操作。我知道VMS在这方面做出了一些令人难以置信的奇怪操作,如果返回除EXIT_SUCCESS或EXIT_FAILURE以外的任何内容,就可能引发问题。 - Leon Timmermans
2
MSDN有不同的说法:当通过mscorlib报告时,退出代码是一个有符号的32位整数。这似乎意味着截断退出代码的C运行时库是有缺陷的。 - user824425
1
是的,这是不正确的。在Windows上返回32位整数(并转换为“无符号”)。在具有32位整数的UNIX系统上也是如此。但任一系统上的UNIX风格的Shell通常仅保留无符号8位整数。 - John McFarlane

5

返回值可被操作系统用来检查程序如何关闭。

在大多数操作系统中,返回值0通常表示正常(至少我所知道的)。

当您自己调用进程并查看程序是否已退出且已正确完成时,也可以检查它。

这不仅仅是一种编程惯例。


问题中没有任何提示表明操作系统存在。在独立系统中返回一个值是没有意义的。 - Lundin

4

main()函数的返回值表示程序的退出状态。如果返回值为0,则表示执行成功;而任何非零值都表示执行过程中发生了错误。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接