ChatGPT解决这个技术问题 Extra ChatGPT

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

在 C 和 C++ 中定义 main() 函数(int main()void main())的正确(最有效)方法是什么?为什么?论点又如何?如果 int main() 那么 return 1 还是 return 0

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

的 main() 函数的有效签名是什么?

main() 函数的返回类型

void main() 和 int main() 之间的区别?

main() 在 C++ 中的签名

main() 的正确声明是什么? — 对于 C++,确实有一个很好的答案。

C 中 main() 函数的样式

C中main()方法的返回类型

中的 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() 的合法定义?

我仍然认为它也相当模糊。为我定义“最有效”。在什么意义上有效?在占用更少内存的意义上?在跑得更快的意义上?我可以看到有用的答案,但我仍然认为这个问题的措辞很糟糕。
Pish posh,高效的上下文在这里很明显,尤其是示例(可能在那里澄清了“高效”的定义)。希望可怜的缓冲区没有爬进洞里,完全后悔这个问题。可以说,无论是 void 还是 int,都会返回一个值,因此它不会影响文件大小、执行的操作或分配的内存。而且,在大多数操作系统中,人们倾向于在成功时返回 0,而在其他成功或失败时返回其他值 - 但没有标准。最终,效率没有任何明显的差异。
“正确(最有效)”没有意义。高效是一回事,正确是另一回事。 main 被调用一次(在 C++ 中只能被调用一次:没有递归)。如果不希望执行在main中花费大量时间,则不要多次调用程序:让程序实现重复。
我发现有趣的是,据我所知,没有一个答案提供了一个完整的示例,包括 #include 语句
返回值在没有操作系统的平台上没有意义。你不会回到任何东西。如果您在嵌入式设备上点击 main(...) 中的 return,您的系统将进入不可预知的状态,您的洗衣机将变得具有自我意识并试图杀死您。因此,我们在这种情况下使用 void main()。这是裸机嵌入式的行业标准做法。

C
Community

main 的返回值指示程序如何退出。正常退出由 main 的 0 返回值表示。非零返回表示异常退出,但对于如何解释非零代码没有标准。正如其他人所指出的,void main() 被 C++ 标准禁止,不应使用。有效的 C++ main 签名是:

int main()

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

这相当于

int main(int argc, char** argv)

还值得注意的是,在 C++ 中,int main() 可以不带返回语句,此时它默认返回 0。对于 C99 程序也是如此。 return 0; 是否应该被省略是有争议的。有效的 C 程序主签名的范围要大得多。

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


main 可以多次进入/离开,但该程序可能不会赢得任何设计奖项;)
C99 还具有 C++ 错误功能,即到达 main() 函数的末尾等同于返回 0——如果 main() 被定义为返回与 int 兼容的类型(第 5.1.2.2.3 节)。
重新输入 main 不是有效的 C++。在标准中明确指出,3.6.1.3 声明“不得在程序中使用 main”
stdlib.h 为此提供了 EXIT_SUCCESS 和 EXIT_FAILURE
和非零是正确的,但对于阅读您的代码的人来说完全没有意义。这个问题证明人们不知道什么是有效/无效代码。 EXIT_SUCCESS/EXIT_FAILURE 更加清晰。
A
Antti Haapala -- Слава Україні

接受的答案似乎是针对 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() 中的 return 语句。如果你这样做了,并且 main() 完成了,那么就会有一个隐含的 return 0

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


@Lundin我认为您不需要引用来说明允许某人制作接受不符合标准的程序的编译器,或者拥有不符合标准的编译器。这是常识和常识
@KABoissonneault 实现定义的行为是标准中的一个术语,而不是完全未记录的行为。如果你实现了一些被列为实现定义行为的东西,你仍然遵循标准。在这种情况下,引用的 C89 没有列出这样的实现定义的行为,因此需要引用,以证明他不是凭空捏造的。
@Lundin你看错了。我们谈论的不是实现定义的行为,我们谈论的是偏离标准的实现,如果他们选择这样做的话。这更像是一个孩子不听父母的话:你不需要父母的一句话来告诉你孩子可以以什么方式违背父母的话。你只知道孩子选择这样做的那一刻,他们不再遵守父母的准则
@KABoissonneault 我在评论中引用的部分绝对是关于实现定义的行为(与非标准编译器扩展相反)。因此我在谈论实现定义的行为。如果您正在对其他事情进行独白,那么祝您好运。
@Lundin 我猜引用中的措辞令人困惑(他们说“但这使得程序实现定义”的部分)但我很确定这个人在谈论非标准行为(如“如果实现允许它”和“不再严格遵守 [to the standard]”),而不是实际的实现定义的行为。该人绝对应该改写他们的答案,但我仍然认为没有必要引用标准
J
Jonathan Leffler

标准 C — 托管环境

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

5.1.2.2.1 程序启动 程序启动时调用的函数名为main。实现没有声明这个函数的原型。它应定义为返回类型为 int 且不带参数: int main(void) { /* ... */ } 或带有两个参数(此处称为 argc 和 argv,尽管可以使用任何名称,如它们对于声明它们的函数是本地的): int main(int argc, char *argv[]) { /* ... */ } 或等效项;10) 或以其他一些实现定义的方式。如果声明了它们,则主函数的参数应遵循以下约束: 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 如果主函数的返回类型是与 int 兼容的类型,则从初始调用主函数的返回等效于以主函数返回的值作为其调用退出函数argument;11) 到达终止 main 函数的 } 返回值 0。如果返回类型与 int 不兼容,则返回给宿主环境的终止状态未指定。 11) 根据 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 退出函数¶5 最后,控制权返回到主机环境。如果 status 的值为零或 EXIT_SUCCESS,则返回实现定义的状态成功终止形式。如果 status 的值为 EXIT_FAILURE,则返回实现定义的状态不成功终止形式。否则返回的状态是实现定义的。

标准 C++ — 托管环境

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

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

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

C++11 标准中的第 18.5 节 开始和终止 段落与 C11 标准中的第 7.22.4.4 节 exit 函数 中的段落相同(上面引用),除了一个脚注(它只是记录了 EXIT_SUCCESSEXIT_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)。

微软 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(),它采用宽字符串,等等。

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

标准 C — 独立环境

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

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

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

¶5 严格遵守的程序应仅使用本国际标准中指定的语言和库的那些特性。3)它不应产生依赖于任何未指定、未定义或实现定义的行为的输出,并且不应超过任何最小实现限制. ¶6 两种形式的一致性实现是托管的和独立的。符合要求的托管实现应接受任何严格符合要求的程序。符合标准的独立实现应接受任何严格符合标准的程序,其中库条款(第 7 条)中指定的功能的使用仅限于标准头文件 。符合标准的实现可以有扩展(包括额外的库函数),只要它们不改变任何严格符合的程序的行为。4)¶7符合标准的程序是符合标准的实现可接受的程序。5)3)严格符合程序可以使用条件特性(参见 6.10.8.3),前提是该使用由使用相关宏的适当条件包含预处理指令保护。例如:#ifdef __STDC_IEC_559__ /* FE_UPWARD 定义 */ /* ... */ fesetround(FE_UPWARD); /* ... */ #endif 4) 这意味着一致的实现不保留除本国际标准中明确保留的标识符之外的标识符。 5) 严格符合的程序旨在最大限度地在符合的实现中移植。符合标准的程序可能取决于符合标准的实现的不可移植特性。

值得注意的是,实际定义任何函数的独立环境所需的唯一标头是 <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 独立实现[合规] 定义了两种实现:托管和独立(1.4)。对于托管实现,本国际标准描述了一组可用的标头。一个独立的实现有一组实现定义的头文件。该集合应至少包括表 16 中所示的标头。提供的标头 版本应至少声明函数 abort、atexit、at_quick_exit、exit 和 quick_exit (18.5)。此表中列出的其他标头应满足与托管实现相同的要求。表 16 — 独立实现的 C++ 头 子条款头 18.2 类型 18.3 实现属性 18.4 整数类型 18.5 开始和终止 18.6 动态内存管理 18.7 类型识别 18.8 异常处理 18.9 初始化列表 18.10 其他运行时支持 20.9 类型特征 29 原子

在 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 和 _Alignof 运算符……¶8 示例 3 在此示例中,计算可变长度数组的大小并从函数返回:#include size_t fsize3(int n) { char b[n +3]; // 变长数组 return sizeof b; // 执行时间 sizeof } int main() { size_t size;大小 = fsize3(10); // fsize3 返回 13 返回 0; }


@DavidBowling:像 int main(){ … } 这样的函数定义确实指定该函数不带参数,但不提供函数原型 AFAICT。对于 main(),这很少是问题;这意味着如果您对 main() 进行递归调用,则不会检查参数。对于其他函数,这更是一个问题——当函数被调用时,你确实需要一个作用域内的原型,以确保参数正确。
@DavidBowling:在 IOCCC 之类的地方之外,您通常不会递归调用 main()。我确实有一个测试程序可以做到这一点——主要是为了新奇。如果您有 int i = 0; int main() { if (i++ < 10) main(i, i * i); return 0; } 并使用 GCC 编译并且不包含 -Wstrict-prototypes,它会在严格的警告下干净地编译。如果是main(void),则编译失败。
我正在阅读 Dennis Ritchie 的“The C Programming Language”,尽管他的 main( ) 函数具有返回值,但他从未在 main( ) 之前加上 int。你知道为什么吗?似乎这里的每个人都在说它应该写成 int main( ),但是 C 的创建者在他关于 ANSI C 的书中并没有这样写。
因为即使是“C 编程语言”的第二版也早于第一个标准 C(我有一份副本,封面的右上角标有“基于提议的 ANSI C 草案”)。在 C90 中,如果返回类型为 int,则不必包含函数的返回类型。如果在没有事先声明的情况下使用函数,则假定它返回 int。但是:C90 标准不是当前标准。目前的标准是 C18,取代 C11 和 C99。 ——— […继续…]
我推荐 King "C Programming: A Modern Approach" 或 Gustedt "Modern C" — 参见 The Definitive C Book Guide and List,这个标题比 Q&A 内容更宏大。
p
phoxis

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


@ChrisYoung 有 EXIT_SUCCESSEXIT_FAILURE 因为某些历史悠久的操作系统(VMS?)使用与 0 不同的数字来表示成功。现在到处都是0。
@FUZxxl 你是对的,但这与我的评论并不冲突。 EXIT_SUCCESS 确实可以不为零,但标准(C89、C99、C11)都将 0(以及 EXIT_SUCCESS)定义为状态成功终止的实现定义形式。
@FUZxxl:确实,VMS 使用奇数(如 1)表示成功,使用偶数(如 0)表示失败。不幸的是,最初的 ANSI C 标准被解释为 EXIT_SUCCESS 必须为 0,因此从 main 返回 EXIT_SUCCESS 在 VMS 上会出现完全错误的行为。为 VMS 做的可移植的事情是使用 exit(EXIT_SUCCESS),它总是做正确的事。
5.1.2.2.3 “如果主函数的返回类型是与int兼容的类型,则从初始调用主函数返回相当于以主函数返回的值作为参数调用退出函数; 11) 到达终止 main 函数的 } 返回值 0。”
然后是 7.22.4.4。关于退出函数:“如果status的值为0或EXIT_SUCCESS,则返回状态成功终止的实现定义形式。如果status的值为EXIT_FAILURE,则返回状态不成功终止的实现定义形式。否则返回的状态是实现定义的。”
Y
Yun

请注意,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() 不再有效。

引入了一个奇怪的、模棱两可的句子“或以某种其他实现定义的方式”。这可以解释为“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 独立式环境

允许使用任何形式或名称的 main 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[])

参考

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 参数,但标准既不祝福也不禁止这种做法;用三个参数定义 main 的程序并不严格符合(参见 §J.5.1.)。

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

如果 main 函数的返回类型是与 int 兼容的类型,则从初始调用到 main 函数的 return 等价于以 main 函数返回的值作为参数调用 exit 函数;11)到达 }终止 main 函数返回值 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 类型的返回类型,否则它的类型是实现定义的。所有实现都应允许以下两种 main 定义:

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

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

ISO 14882:2003 3.6.1 主要功能

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

ISO 14882:2011 3.6.1 主要功能

实现不应预定义主要功能。该功能不得重载。它应该有一个 int 类型的返回类型,否则它的类型是实现定义的。所有实现都应允许 - () 的函数返回 int 和 - (int, 指向 char 的指针的指针) 的函数将 int 作为 main (8.3.5) 的类型返回。

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 它可以有任何签名,只要它不被命名为 main() 因为它必须使用列出的签名之一。我想最常见的是void my_startup_function (),因为从独立系统上的程序返回是没有意义的。
我懂了。但是如果允许对启动函数使用任何名称和任何签名,为什么不允许对 main 使用不同的签名呢?抱歉,如果这不是一个聪明的问题,但我无法理解背后的原因。
@Utku C 和 C++ 在那里是不同的。至于为什么 C++ 强制执行这一点,我不知道,没有理由。我怀疑罪魁祸首(双关语)是Stroustrup,他很早就宣布 main 必须返回 int, period。因为当他制作第一个 C++ 版本时,他只习惯于托管系统。在链接的帖子中,Stroustrup仍然似乎没有注意到独立实现的存在:例如,他无知地引用了 C 标准的托管实现子章,而忽略了第 5.1.2.1 章的存在。
关于 C11 标准草案的值得注意的一点是,尽管 func() 被认为已过时,但草案本身在其自己的示例中使用了 int main()
L
Lou Franco

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


J
Jeegar Patel

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

return 1? return 0?

如果 int main() 中不写 return 语句,则结尾的 } 默认会返回 0。

(仅在 c++ 和 c99 以后,对于 c90,您必须编写 return 语句。请参阅 Why main does not return 0 here?

return 0 或 return 1 将被父进程接收。在 shell 中,它进入一个 shell 变量,如果您从 shell 运行程序而不使用该变量,那么您不必担心 main() 的返回值。

请参阅How can I get what my main function has returned?

$ ./a.out
$ echo $?

这样,您可以看到变量 $? 接收 main() 的返回值的最低有效字节。

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


严格来说,$? 不是环境变量;它是一个 shell 预定义(或内置)变量。区别很难发现,但如果您运行 env(不带任何参数),它会打印环境,而 $? 不会显示在环境中。
仅在 C++ 和 C99 及更高版本中,而不是在 C90 中,当主要“结束”时自动返回 0。
@Kaz 是的,我已经相应地更新了答案,实际上我已经问过这个问题stackoverflow.com/questions/8677672/…
F
Ferruccio

请记住,即使您返回一个 int,某些操作系统 (Windows) 也会将返回的值截断为单个字节 (0-255)。


Unix 也是如此,大多数其他操作系统可能也是如此。我知道 VMS 用它做了如此不可思议的奇怪事情,以至于返回 EXIT_SUCCESS 或 EXIT_FAILURE 以外的任何东西都是自找麻烦。
MSDN 要求不同:当通过 mscorlib 报告时,an exit code is a signed 32-bit integer。这似乎意味着截断退出代码的 C 运行时库 是有缺陷的。
是的,这是不正确的。在 Windows 上,返回一个 32 位整数(并转换为 unsigned)。在具有 32 位整数的 UNIX 系统上也是如此。但是任何一个系统上的 UNIX 风格的 shell 通常只保留一个无符号的 8 位整数。
P
Peter Mortensen

操作系统可以使用返回值来检查程序是如何关闭的。

返回值 0 在大多数操作系统中通常意味着 OK(无论如何我都能想到的那些)。

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

这不仅仅是一个编程约定。


问题中没有任何内容表明存在操作系统。在独立系统中返回值没有任何意义。
P
Peter Mortensen

main() 的返回值显示程序是如何退出的。如果返回值为 zero,则表示执行成功,而任何非零值都表示执行中出现问题。


这是评论而不是问题的答案。
P
Peter Mortensen

返回 0 应该告诉程序员程序已成功完成工作。


main() 返回 1 通常表示发生了错误;返回 0 表示成功。如果你的程序总是失败,那么 1 是可以的,但这不是最好的主意。
@JonathanLeffler:从 main 返回 1 的含义是实现定义的。唯一由语言定义的值是 0EXIT_SUCCESS(通常定义为 0)和 EXIT_FAILURE。在 OpenVMS 中,return 1; 表示成功 终止。
VMS 不是“正常的”——正如我所说的那样。这难道不是类似于“任何奇怪的价值都是成功吗?甚至值在 VMS 上都是失败的?
E
Edward

省略返回 0

当 C 或 C++ 程序到达 main 的末尾时,编译器将自动生成返回 0 的代码,因此无需将 return 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 函数的末尾写明确的 return; 语句。反对省略的原因似乎可以归结为 "it looks weird"。如果您像我一样对更改为 C 标准 read this question 的理由感到好奇。另请注意,在 1990 年代初期,这被认为是“草率的做法”,因为当时它是未定义的行为(尽管得到广泛支持)。

此外,C++ Core Guidelines 包含多个在 main 末尾省略 return 0; 的实例,并且没有写入显式返回的实例。尽管该文件中还没有关于这个特定主题的具体指导方针,但这似乎至少是对这种做法的默许。

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


注意:此答案的目的是让我们这些经常在 CodeReview 上提供此建议的人提供 StackOverflow 答案,我们可以指出关于省略 return 0; 的做法
这是一个糟糕的建议,因为仅实现 C89 而不是任何后续标准的编译器仍然非常普遍(我在 2017 年编写此代码),并且在可预见的未来仍将非常普遍。例如,最后我检查了没有实现 C99 的 Microsoft 编译器版本,据我了解,这对于非 GCC 的嵌入式系统编译器仍然很典型。
@zwol:任何别无选择,只能使用已过时 28 年的编译器的人可能比决定是否显式包含 return 0; 有更多问题,但我会注意到那个时代的许多编译器也实现了隐式 { 1} 甚至在它被标准化之前。
你说的是真的。我的意思只是为“糟糕的建议”反应提供一个理由,而不仅仅是“它看起来很奇怪”。
实际上,我做了很多嵌入式系统工作,十多年来没有遇到过不支持隐式 return 0 的编译器。也是当前版本的 Microsoft C support it as well。也许您的信息已过时?
p
phoxis

返回的内容取决于您要对可执行文件执行的操作。例如,如果您将程序与命令行 shell 一起使用,那么您需要返回 0 表示成功,返回非 0 表示失败。然后,您将能够根据代码的结果在带有条件处理的 shell 中使用该程序。您也可以根据您的解释分配任何非零值,例如对于严重错误,不同的程序退出点可以终止具有不同退出值的程序,并且调用 shell 可以通过检查返回的值来决定做什么。如果代码不打算与 shell 一起使用并且返回的值不会打扰任何人,那么它可能会被省略。我个人使用签名 int main (void) { .. return 0; .. }


main() 的格式由实现决定,即编译器。程序员不会选择选择哪种形式,除非编译器支持多种形式。
@Lundin 返回类型将由实现实现。但是要返回的值是由程序员决定的。 C99 第 5.1.2.2.3 节提到 main 的返回类型与 int 兼容。因此返回 int 不会有问题。虽然允许其他返回类型,但在这种情况下,具有返回值的环境变量将是未指定的。但是如果程序员做了return 0;,那么在 bash 中它可以用来创建分支。
P
Peter Mortensen

如果您确实存在与从进程返回整数的效率相关的问题,您可能应该避免多次调用该进程以致该返回值成为问题。

如果你这样做(多次调用一个进程),你应该找到一种方法将你的逻辑直接放在调用者中,或者放在一个 DLL 文件中,而不为每次调用分配一个特定的进程;在这种情况下,多进程分配给您带来了相关的效率问题。

详细地说,如果您只想知道返回 0 是否比返回 1 效率更高或更低,在某些情况下它可能取决于编译器,但一般来说,假设它们是从相同的源(本地、字段、常量、嵌入式在代码、函数结果等中)它需要完全相同数量的时钟周期。


P
Peter Mortensen

这是返回码用法的一个小演示......

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

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

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

$ grep order myfile

如果你这样做

$ echo $?
$ 0

你会得到一个0。为什么?因为 grep 找到了匹配项并返回了退出代码 0,这是成功退出的常用值。让我们再次检查一下,但有些内容不在我们的文本文件中,因此找不到匹配项:

$ grep foo myfile
$ echo $?
$ 1

由于 grep 未能将令牌“foo”与我们文件的内容匹配,因此返回码为 1(这是发生故障时的常见情况,但如上所述,您有很多值可供选择)。

现在下面的 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() 的返回值)。


在 shell 脚本中,您将使用 if grep foo myfile; then echo 'Match found'; else echo 'No match was found'; fi — 直接测试返回状态。如果您想捕获状态(用于报告等),那么您确实使用分配。您可以使用 if grep foo myfile; CHECK=$?; [ "$CHECK" = 0 ]; then echo 'Match found'; else echo 'No match was found'; fi 或使用三行。您还可以使用选项 -s-qgrep 来防止出现匹配或常规错误消息。然而,这是 shell 的细节——关键点,退出状态很有用——是可以的。
S
Steve Summit

在 C 和 C++ 中定义 main() 函数的正确(最有效)方法是什么——int main() 或 void main()——为什么?

那些词“(最有效)”并没有改变问题。除非您处于独立环境中,否则有一种普遍正确的方式来声明 main(),即返回 int。

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

这不是应该 main()返回的,而是应该 main()返回的。 main() 当然是别人调用的函数。您无法控制调用 main() 的代码。因此,您必须使用类型正确的签名声明 main() 以匹配其调用者。你在这件事上根本没有任何选择。您不必问自己什么效率更高或更低,或风格更好或更差,或类似的事情,因为答案已经由 C 和 C+ 标准完美定义。只要跟着他们。

如果 int main() 那么返回 1 还是返回 0?

表示成功,非零表示失败。同样,不是您需要(或获得)选择的东西:它是由您应该遵守的接口定义的。


g
gsamaras

在 C 中,Section 5.1.2.2.1 of the C11 standard(强调我的):

它应定义为返回类型为 int 且不带参数: int main(void) { /* ... */ } 或带有两个参数(此处称为 argc 和 argv,尽管可以使用任何名称,如它们对于声明它们的函数是本地的): int main(int argc, char *argv[]) { /* ... */ }

但是对于像我这样的初学者来说,一个抽象的例子可以让我掌握它:

当您在程序中编写一个方法时,例如int read_file(char filename[LEN]);,您希望作为该方法的调用者知道一切是否顺利(因为可能会发生故障,例如找不到文件)。通过检查方法的返回值,您可以知道一切是否顺利,这是方法向您发出成功执行(或不成功)信号的一种机制,并让调用者(您,例如在您的 main 方法中)决定如何处理意外故障。

所以现在想象我为一个用于更复杂系统的微机制编写了一个 C 程序。当系统调用微机制时,它想知道一切是否按预期进行,以便它可以处理任何潜在的错误。如果 C 程序的 main 方法会返回 void,那么调用系统如何知道其子系统(微机制)的执行情况?它不能,这就是 main() 返回 int 的原因,以便与调用者通信成功(或不成功)执行。

换句话说:

合理的是主机环境(即操作系统(OS))需要知道程序是否正确完成。如果没有 int 兼容类型作为返回类型(例如 void),“返回到主机环境的状态是未指定的”(即大多数操作系统上的未定义行为)。


D
Dwedit

在 Windows 上,如果程序由于访问冲突而崩溃,退出代码将为 STATUS_ACCESS_VIOLATION (0xC0000005)。对于来自 x86 异常的其他类型的崩溃也类似。

因此,除了您从 main 返回或传递给 exit 的内容之外,还有一些事情可能会导致看到退出代码。


这个问题是关于 main 返回什么;不是程序可以完成的其他方式
投删除投票,因为此答案不会尝试回答问题。