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

What should main() return in C and C++?

提问人:Joel 提问时间:10/15/2008 最后编辑:DharmanJoel 更新时间:9/26/2023 访问量:453364

问:

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

C++ C 程序入口点 返回类型

评论

38赞 Onorio Catenacci 10/16/2008
我仍然认为它也相当模糊。为我定义“最有效”。从什么意义上说是有效的?从占用更少内存的意义上说?从跑得更快的意义上说?我可以看到有用的答案,但我仍然认为这个问题的措辞很差。
13赞 Kit10 12/12/2012
Pish posh,高效的上下文在这里是显而易见的,尤其是对于示例(这些示例可能用于澄清“高效”的定义)。希望可怜的缓冲区没有爬进洞里,完全后悔这个问题。可以说,无论 void 还是 int,都会返回一个值,因此它对文件大小、执行的操作或分配的内存没有影响。在大多数操作系统中,人们倾向于在成功时返回 0,而在其他方面返回其他成功或失败,但没有标准。最终,效率没有任何明显的差异。
0赞 Kaz 10/18/2013
“正确(最有效)”没有意义。高效是一回事,正确是另一回事。 被调用一次(在 C++ 中只能调用一次:没有递归)。如果你不希望执行花费大量时间,那么就不要大量调用程序:让程序实现重复。mainmain
3赞 puk 11/12/2013
我发现有趣的是,据我所知,没有一个答案提供了一个完全可行的例子,包括陈述#include
7赞 3Dave 6/26/2019
返回值在没有操作系统的平台上没有意义。你不会回到任何事情。如果你在嵌入式设备上碰到,你的系统就会进入一个不可预测的状态,你的洗衣机会变得有自我意识并试图杀死你。因此,我们在这种情况下使用。这是裸机嵌入式的行业标准做法。returnmain(...)void main()

答:

658赞 workmad3 10/15/2008 #1

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

int main(void)

int main(int argc, char **argv)

这相当于

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

还值得注意的是,在 C++ 中,可以不保留 return-statement,此时它默认返回 0。C99 程序也是如此。是否应该省略还有待商榷。有效的 C 程序主签名的范围要大得多。int main()return 0;

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

评论

86赞 korona 10/15/2008
main 可以多次输入/离开,但该程序可能不会赢得任何设计奖;)
20赞 Jonathan Leffler 10/15/2008
C99 还具有 C++ 错误功能,即到达 main() 函数的末尾等同于返回 0——如果 main() 被定义为返回与 int 兼容的类型(第 5.1.2.2.3 节)。
73赞 workmad3 10/15/2008
重新输入 main 无效的 C++。在标准中,3.6.1.3 明确规定“不得在程序中使用 main”
139赞 Clay 10/15/2008
stdlib.h 为此目的提供了EXIT_SUCCESS和EXIT_FAILURE
31赞 JaredPar 10/16/2008
0 和非零是正确的,但对于阅读代码的人来说完全没有意义。这个问题证明人们不知道什么是有效/无效代码。EXIT_SUCCESS/EXIT_FAILURE要清楚得多。
32赞 Lou Franco 10/15/2008 #2

成功时返回 0,错误时返回非零。这是 UNIX 和 DOS 脚本用来找出程序发生的情况的标准。

67赞 dmityugov 10/15/2008 #3

我相信应该返回或.它们在main()EXIT_SUCCESSEXIT_FAILUREstdlib.h

评论

4赞 fuz 9/17/2014
@ChrisYoung 有 和 因为某些历史操作系统 (VMS?) 使用与 0 不同的数字来表示成功。现在到处都是 0。EXIT_SUCCESSEXIT_FAILURE
8赞 Chris Young 10/22/2014
@FUZxxl你是对的,但这与我的评论并不冲突。EXIT_SUCCESS确实可以是非零,但标准(C89、C99、C11)都将 0(以及 EXIT_SUCCESS)定义为状态成功终止的实现定义形式。
2赞 Adrian McCarthy 11/17/2016
@FUZxxl:VMS 确实使用奇数值(如 1)表示成功,使用偶数值(如 0)表示失败。不幸的是,最初的 ANSI C 标准被解释为意味着 EXIT_SUCCESS 必须为 0,因此从 main 返回 EXIT_SUCCESS 在 VMS 上的行为完全错误。对于VMS来说,可移植的事情是使用,它总是做正确的事情。exit(EXIT_SUCCESS)
1赞 Lundin 12/15/2017
5.1.2.2.3 “如果main函数的返回类型是与int兼容的类型,则从初始调用main函数返回相当于调用退出函数,并将main函数返回的值作为其参数;11) 到达终止主函数的 } 返回值 0。
1赞 Lundin 12/15/2017
然后是 7.22.4.4。关于退出函数:“如果 status 的值为 0 或 EXIT_SUCCESS,则返回状态成功终止的实现定义形式。如果 status 的值为 EXIT_FAILURE,则返回状态 unsuccessful terminateation 的实现定义形式。否则,返回的状态是实现定义的。
8赞 Ferruccio 10/15/2008 #4

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

评论

5赞 Leon Timmermans 10/17/2008
Unix 和大多数其他操作系统一样做同样的事情。我知道 VMS 用它做了如此令人难以置信的奇怪事情,以至于返回除 EXIT_SUCCESS 或EXIT_FAILURE之外的任何东西都是自找麻烦。
2赞 1/5/2015
MSDN 有所不同:当通过 mscorlib 报告时,退出代码是一个有符号的 32 位整数。这似乎意味着截断退出代码的 C 运行时库是有缺陷的。
1赞 John McFarlane 7/30/2017
是的,这是不正确的。在 Windows 上,返回 32 位整数(并转换为 )。这在具有 32 位整数的 UNIX 系统上是一样的。但是,任一系统上的 UNIX 样式 shell 通常只保留一个无符号的 8 位整数。unsigned
218赞 Chris Young 10/16/2008 #5

公认的答案似乎是针对 C++ 的,所以我想我会添加一个与 C 相关的答案,这在几个方面有所不同。ISO/IEC 9899:1989 (C90) 和 ISO/IEC 9899:1999 (C99) 之间也有一些变化。

main()应声明为:

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

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

如果实现允许,可以用其他方式声明(例如,),但这会使程序实现被定义,并且不再严格遵守。main()int main(int argc, char *argv[], char *envp[])

该标准定义了 3 个严格符合(即不依赖于实现定义的行为)的返回值:成功终止和不成功终止。任何其他值都是非标准的,并且是已定义的实现。在 C90 中,必须在末尾有一个明确的语句,以避免未定义的行为。在 C99 及更高版本中,可以省略 return 语句。如果你这样做了,并且完成了,就会有一个隐含的.0EXIT_SUCCESSEXIT_FAILUREmain()returnmain()main()return 0

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

评论

1赞 KABoissonneault 7/7/2015
@Lundin 我不认为你需要一句话来说明有人被允许制作一个接受不符合标准的程序的编译器,或者有一个不符合标准的编译器。这是常识和常识
5赞 Lundin 7/8/2015
@KABoissonneault 实现定义的行为是标准中的一个术语,而不是完全未记录的行为。如果实现列为实现定义的行为,则仍遵循标准。在这种情况下,引用的 C89 没有列出这种实现定义的行为,因此需要引用来证明他不仅仅是突然编造的。
1赞 KABoissonneault 7/8/2015
@Lundin 你看错了。我们谈论的不是实现定义的行为,我们谈论的是偏离标准的实现,如果他们选择这样做的话。这更像是一个孩子不服从父母:你不需要父母的一句话来告诉你孩子可以以什么方式违背父母所说的话。你只知道,当孩子选择这样做的那一刻,他们就不再遵守父母的习惯
2赞 Lundin 7/8/2015
@KABoissonneault 我在评论中引用的部分肯定是关于实现定义的行为(而不是非标准的编译器扩展)。因此,我说的是实现定义的行为。如果你正在对其他事情进行独白,祝你好运。
2赞 KABoissonneault 7/8/2015
@Lundin 我猜引文中的措辞令人困惑(他们说“但这使得程序实现被定义”的部分),但我很确定这个人是在谈论非标准行为(如“如果实现允许”和“不再严格遵守[标准]”中所说),而不是实际实现定义的行为。这个人绝对应该改写他们的答案,但我仍然认为没有必要引用标准中的一句话
2赞 Luca C. 3/3/2011 #6

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

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

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

2赞 phoxis 3/10/2011 #7

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

评论

0赞 Lundin 12/15/2017
main() 的格式由实现(即编译器)决定。程序员不会选择选择哪种形式,除非编译器支持多种形式。
0赞 phoxis 12/19/2017
@Lundin 返回类型为实现实现。但是要返回的值是由程序员决定的。C99 第 5.1.2.2.3 节提到 的返回类型与 兼容。因此,返回不会有问题。尽管允许其他返回类型,但在这种情况下,将未指定具有返回值的环境变量。但是,如果程序员这样做,那么在 bash 中,它可以用来创建分支。mainintintreturn 0;
5赞 Yochai Timmer 7/2/2011 #8

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

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

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

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

评论

0赞 Lundin 7/7/2015
问题中没有任何内容表明存在操作系统。在独立系统中返回值没有任何意义。
4赞 user379888 7/2/2011 #9

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

11赞 Jeegar Patel 12/27/2011 #10

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

return 1? return 0?
  1. 如果不在 中编写 return 语句,则关闭将默认返回 0。int main()}

(仅在 c++ 和 c99 及更高版本中,对于 c90,您必须编写 return 语句。请在此处查看为什么 main 不返回 0?)

  1. return 0或将由父进程接收。在 shell 中,它进入 shell 变量,如果您从 shell 运行程序并且不使用该变量,则无需担心 的返回值。return 1main()

请参阅如何获取我的主函数返回的内容?

$ ./a.out
$ echo $?

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

在 Unix 和 DOS 脚本中,通常返回 on success 和 non-zero 表示错误。这是 Unix 和 DOS 脚本使用的标准,用于找出程序发生了什么并控制整个流程。return 0

评论

4赞 Jonathan Leffler 9/23/2013
严格来说,不是一个环境变量;它是一个 shell 预定义(或内置)变量。这种差异很难发现,但如果运行(没有任何参数),它会打印环境,并且不会显示在环境中。$?env$?
1赞 Kaz 10/18/2013
当主要“结束”仅在 C++ 和 C99 及更高版本时自动返回 0,而不是在 C90 中。
0赞 Jeegar Patel 10/25/2021
@Kaz是的,我已经相应地更新了答案,实际上我已经问过这个问题 stackoverflow.com/questions/8677672/......
3赞 Vamsi Pavan Mahesh 11/23/2012 #11

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

评论

1赞 Jonathan Leffler 9/23/2013
返回 1 表示发生错误;返回 0 表示成功。如果你的程序总是失败,那么 1 是可以的,但这不是最好的主意。main()
3赞 Keith Thompson 2/6/2014
@JonathanLeffler:返回 from 的含义是实现定义的。语言定义的唯一值是 、(通常定义为 )、和 。在 OpenVMS 中,表示成功终止。1main0EXIT_SUCCESS0EXIT_FAILUREreturn 1;
0赞 Jonathan Leffler 2/6/2014
VMS不是“正常的”——在我所说的意思里。这难道不是“任何奇数值都是成功;甚至值在 VMS 上都是失败的?
147赞 Jonathan Leffler 9/10/2013 #12

标准 C — 托管环境

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

5.1.2.2.1 程序启动

程序启动时调用的函数名为 。实现声明否 此函数的原型。它的定义应为返回类型 和 不 参数:mainint

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

或具有两个参数(此处称为 和 ,尽管任何名称都可以是 使用,因为它们是声明它们的函数的本地函数):argcargv

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

或同等学历;10) 或以其他实现定义的方式。

如果声明了它们,则 main 函数的参数应遵循以下内容 约束:

  • 的值应为非负数。argc
  • argv[argc]应为 null 指针。
  • 如果 的值大于零,则数组成员通过 inclusive 应包含指向字符串的指针,这些字符串被给出 在程序启动之前由主机环境实现定义的值。这 目的是向程序提供在程序启动之前确定的信息 从托管环境中的其他位置。如果主机环境无法 提供带有大写和小写字母的字符串,实现 应确保以小写形式接收字符串。argcargv[0]argv[argc-1]
  • 如果 的值大于零,则指向的字符串表示程序名称; 如果 程序名称在主机环境中不可用。如果 的值为 大于 1,则 through 指向的字符串表示程序参数。argcargv[0]argv[0][0]argcargv[1]argv[argc-1]
  • 数组指向的参数和字符串应 可由程序修改,并在程序之间保留其最后存储的值 启动和程序终止。argcargvargv

10)因此,可以用定义为 的 typedef 名称替换,或者 的类型可以写成 ,依此类推。intintargvchar **argv

程序在 C99 或 C11 中终止

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

5.1.2.2.3 程序终止

1 如果函数的返回类型是兼容的类型,则从 对函数的初始调用等同于使用以下值调用函数 由函数作为其参数返回;11) 到达终止函数将返回值 0。如果返回类型与 不兼容, 返回到主机环境的终止状态未指定。mainintmainexitmain}mainint

11) 根据 6.2.4,在前一种情况下,具有自动存储期限的对象的生命周期将结束,即使在后一种情况下不会结束。main

请注意,这是“成功”的授权。如果您愿意,可以使用 and,但 0 是公认的,1 也是如此。另请参阅大于 255 的退出代码 — 可能?0EXIT_FAILUREEXIT_SUCCESS<stdlib.h>

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

7.22.4.4 函数exit

¶5 最后,将控制权交还给主机环境。如果 的值为零或 ,则返回状态成功终止的实现定义形式。如果值为 ,则返回状态 unsuccessful terminateation 的实现定义形式。否则,返回的状态为实现定义的状态。statusEXIT_SUCCESSstatusEXIT_FAILURE

标准 C++ — 托管环境

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

3.6.1 Main 函数 [basic.start.main]

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

¶2 实现不应预定义 main 函数。此功能不得过载。它应该 返回类型为 int 类型,但其类型为 implementation defined。 所有实现 应允许以下两个 main 定义:

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

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

后一种形式应是从环境传递到程序的参数数 运行程序的地方。如果为非零,则这些参数应作为指向以 null 结尾的多字节字符串 (NTMBS) 的初始字符的指针提供 (17.5.2.1.4.2),并且应是指向表示 用于调用程序的名称或 。的值应为非负数。的值应为 0。[注意:建议在 之后添加任何其他(可选)参数。 —end 注意]argcargcargv[0]argv[argc-1]argv[0]""argcargv[argc]argv

¶3 该函数不得在程序中使用。的链接 (3.5) 是实现定义的。[...]mainmain

¶5 main 中的 return 语句具有离开 main 函数的效果(使用 automatic 销毁任何对象 storage duration) 并使用返回值作为参数进行调用。如果控制到达终点 的 main 而不遇到 return 语句,效果是执行std::exit

return 0;

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

C++11 标准中有一段 §18.5 开始和终止,与 C11 标准(如上引用)中的 §7.22.4.4 退出函数中的段落相同,除了脚注(仅记录并在 中定义)。EXIT_SUCCESSEXIT_FAILURE<cstdlib>

标准 C — 通用扩展

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

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

第三个参数是指向字符串的以 null 结尾的指针列表,每个字符串都是一个环境变量,该变量具有名称、等号和值(可能为空)。如果你不使用它,你仍然可以通过''访问环境。这个全局变量在 POSIX 中的变量中是独一无二的,因为它没有声明它的标头。extern char **environ;

这被 C 标准认可为通用扩展,记录在附录 J 中:

###J.5.1 环境参数

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

Microsoft C

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

main 的声明语法是

 int main();

或者,可选地,

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

或者,可以将 and 函数声明为返回(无返回值)。如果声明 或 返回 void,则不能使用 return 语句将退出代码返回到父进程或操作系统。若要在 或 声明为 时返回退出代码,必须使用该函数。mainwmainvoidmainwmainmainwmainvoidexit

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

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

Microsoft页面还列出了其他一些替代方案 - 需要宽字符串,等等。wmain()

此页的 Microsoft Visual Studio 2005 版本未作为替代项列出。从 Microsoft Visual Studio 2008 开始的版本确实如此。void main()

标准 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><stddef.h><stdint.h><stdnoreturn.h>

¶7 符合要求的程序是符合要求的实现可以接受的程序五)

3) 严格符合的程序可以使用条件特征(参见 6.10.8.3),前提是使用相关宏的适当条件包含预处理指令保护使用。例如:

#ifdef __STDC_IEC_559__ /* FE_UPWARD defined */
    /* ... */
    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 中所示的标头。

提供的标头版本应至少声明函数 、 、 和 (18.5)。此表中列出的其他标头应满足与托管实现相同的要求。<cstdlib>abortatexitat_quick_exitexitquick_exit

表 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 显示了首选符号 — — 但标准中也有两个示例显示:§6.5.3.4 ¶8 和 §6.7.6.3 ¶20。现在,重要的是要注意,例子不是“规范的”;它们只是说明性的。如果示例中存在错误,它们不会直接影响标准的正文。也就是说,它们强烈地表明了预期行为,因此,如果标准包含在示例中,则表明即使它不是首选符号,也不会被禁止。int main(void)int main()int main()int main()

6.5.3.4 和运算符sizeof_Alignof

...

¶8 示例 3 在此示例中,计算可变长度数组的大小并从函数返回:

#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;
}

函数定义 like 指定函数不带参数,但不提供函数原型 AFAICT。因为这很少是问题;但这确实意味着,如果您有递归调用 ,则不会检查参数。对于其他函数,这更像是一个问题——当调用函数时,你确实需要一个范围内的原型,以确保参数是正确的。int main(){ … }main()main()

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

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

并使用 GCC 编译并且不包括 ,它会在严格的警告下干净地编译。如果是 ,则编译失败,因为函数定义说“没有参数”。-Wstrict-prototypesmain(void)

评论

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

下面是一个返回代码用法的小演示......

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

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

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

$ grep order myfile

如果你这样做

$ echo $?
$ 0

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

让我们再检查一次,但有些东西不在我们的文本文件中,因此找不到匹配项:

$ grep foo myfile
$ echo $?
$ 1

由于 grep 未能将标记“foo”与我们文件的内容匹配,因此返回码为 1(这是发生故障时的常见情况,但如上所述,您有很多值可供选择)。同样,如果我们把它放在简单的布尔上下文中(一切都好与否),否定 1(布尔值 true)会产生一个 0(布尔值 false),这同样可以通过 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。第二个条件语句在最后一行中回显其消息,因为它是 true,因为 CHECK == 1。

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

评论

1赞 Jonathan Leffler 2/17/2019
在 shell 脚本中,您将使用 — 直接测试返回状态。如果要捕获状态(用于报告等),则可以使用分配。您可以使用或可能使用三行。您还可以使用选项 和 来防止出现匹配项或例行错误消息。然而,这是 shell 的细节——关键点,退出状态可能有用——是可以的。if grep foo myfile; then echo 'Match found'; else echo 'No match was found'; fiif grep foo myfile; CHECK=$?; [ "$CHECK" = 0 ]; then echo 'Match found'; else echo 'No match was found'; fi-s-qgrep
50赞 Lundin 7/7/2015 #14

请注意,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 删除了“implicit int”,因此不再有效。main()

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

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

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

然而,托管环境程序终止部分随后继续争论 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


请注意,在上述任何版本中,从未将其列为任何托管 C 实现的有效形式。在 C 中,与 C++ 不同,具有不同的含义。前者是一个过时的功能,可能会从语言中删除。请参阅 C11 未来的语言方向:int main()()(void)

6.11.6 函数声明符

使用带有空括号的函数声明符(不是原型格式的参数类型声明符)是一个过时的功能。


  • C++03 托管环境

允许的表格 9

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

评论:

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

  • C++03 独立环境

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

// 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 独立环境

启动时调用的函数的名称是实现定义的。如果它被命名,它必须遵循规定的表格 12main()

// 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) 或在一些其他实现中定义 方式。

  1. 《国际标准——编程语言——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] 必须是 null 指针,以便为列表末尾提供冗余检查,这也是基于常见做法。

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

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

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

  1. ISO 9899:1999 5.1.2.1 独立式环境

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

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

本节与上面引用的 C99 相同。

  1. ISO 9899:1999 5.1.2.1 独立式环境

本节与上面引用的 C99 相同。

  1. ISO 14882:2003 3.6.1 主要功能

实现不应预定义 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 主要功能

实现不应预定义 main 函数。此功能不得过载。它的返回类型应为 int 类型,但除此之外,其类型是实现定义的。所有实现应 允许两者

— () 的函数,返回 int 和

— 返回 int 的函数 (int, pointer to pointer to char)

作为 main 的类型 (8.3.5)。

  1. ISO 14882:2011 3.6.1 主要功能

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

评论

0赞 Utku 11/17/2015
一个问题:C++ 标准是否意味着独立环境中启动函数的签名也定义了实现?例如,一个实现可以将启动函数定义为:或者,但它是否可以,例如:作为启动函数?我想不是,对吧?另外,这不也是模棱两可的吗?int my_startup_function ()int my_startup_function (int argc, char *argv[])char my_startup_function (long argc, int *argv[])
0赞 Lundin 11/17/2015
@Utku 它可以有任何签名,只要它没有被命名,因为它必须使用列出的签名之一。我想最常见的是 ,因为在独立系统上从程序返回是没有意义的。main()void my_startup_function ()
1赞 Utku 11/17/2015
明白了。但是,如果允许对启动函数使用任何名称和任何签名,为什么不允许也使用不同的签名呢?对不起,如果这不是一个聪明的问题,但我无法理解背后的原因。main
0赞 Lundin 11/17/2015
@Utku C 和 C++ 在那里是不同的。至于为什么C++强制执行这一点,我不知道,没有理由。我怀疑罪魁祸首(双关语)是 Stroustrup,他很早就宣布 main 必须返回 int,句点。因为当他制作第一个 C++ 版本时,他只习惯于托管系统。在链接的帖子中,Stroustrup 似乎仍然对独立实现的存在视而不见:例如,他无知地提到了 C 标准的托管实现子章节,而忽略了第 5.1.2.1 章的存在。
1赞 Antti Haapala -- Слава Україні 3/13/2016
关于 C11 标准草案的值得注意的一点是,尽管该草案被认为已经过时,但该草案本身在自己的示例中使用。func()int main()
4赞 Edward 4/22/2017 #15

省略return 0

当一个 C 或 C++ 程序到达末尾时,编译器会自动生成返回 0 的代码,因此无需显式放在 的末尾。mainreturn 0;main

注意:当我提出这个建议时,它几乎总是伴随着两种评论之一:“我不知道”或“这是糟糕的建议!我的基本原理是,依赖标准明确支持的编译器行为是安全且有用的。对于C,自C99起;请参阅 ISO/IEC 9899:1999 第 5.1.2.2.3 节:

[...]从对函数的初始调用返回等同于使用函数返回的值作为其参数来调用函数;达到终止函数将返回值 0。mainexitmain}main

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

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

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

此外,C++ 核心准则在末尾包含多个省略实例,并且没有写入显式返回的实例。尽管该文件中尚未就这一特定主题制定具体准则,但这似乎至少是对这种做法的默许。return 0;main

所以我主张省略它;其他人不同意(通常是激烈的!无论如何,如果你遇到省略它的代码,你就会知道它受到标准的明确支持,你就会知道它的含义。

评论

1赞 Edward 4/24/2017
注意:这个答案的目的是让我们这些经常在 CodeReview 上给出这个建议的人得到一个 StackOverflow 答案,我们可以指出关于省略的做法return 0;
3赞 zwol 5/14/2017
这是一个不好的建议,因为只实现 C89 而不是任何更高标准的编译器仍然非常普遍(我在 2017 年写了这篇文章),并且在可预见的未来仍然非常普遍。例如,上次我检查没有版本的 Microsoft 编译器实现了 C99,据我所知,这对于非 GCC 的嵌入式系统编译器来说仍然很常见。
7赞 Edward 5/14/2017
@zwol:任何别无选择,只能使用过时 28 年的编译器的人,可能比决定是否显式包含更多的问题,但是我要指出的是,那个时代的许多编译器甚至在标准化之前就实现了隐式。return 0;return 0;
0赞 zwol 5/14/2017
你说的是真的。我的意思是只是为“糟糕的建议”反应给出一个理由,而不仅仅是“它看起来很奇怪”。
2赞 Edward 5/14/2017
实际上,我做了很多嵌入式系统的工作,并且已经十多年没有遇到过不支持隐式的编译器了。此外,Microsoft C的当前版本也支持它。也许您的信息已过时?return 0
4赞 Steve Summit 10/4/2017 #16

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

“(最有效)”这几个字并不能改变问题。除非你处于一个独立的环境中,否则有一种普遍正确的声明方式,那就是返回。main()int

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

一个,纯粹而简单。这不仅仅是“应该归还的东西”,而是“必须归还的东西”。 当然,是其他人调用的函数。您无法控制调用 .因此,必须使用类型更正签名进行声明以匹配其调用方。在这件事上,你根本别无选择。你不必问自己什么效率更高或更低,或者什么风格更好或更差,或者类似的东西,因为答案已经完全定义了,对你来说,按照 C 和 C+ 标准。只要跟着他们走。intmain()main()main()mainmain

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

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

0赞 gsamaras 10/10/2020 #17

在 C 中,C11 标准的第 5.1.2.2.1 节(强调我的):

它应使用 int 的返回类型定义,并且不定义 参数:

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

或具有两个参数(此处称为 和 ,虽然 可以使用任何名称,因为它们是它们所在的函数的本地名称 声明):argcargv

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

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

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

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

换言之:

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

-4赞 Dwedit 8/30/2021 #18

在 Windows 上,如果程序因访问冲突而崩溃,则退出代码将为 。与 x86 异常引起的其他类型的崩溃类似。STATUS_ACCESS_VIOLATION (0xC0000005)

因此,除了返回或传递的内容之外,还有其他内容可能会导致看到退出代码。mainexit

评论

1赞 M.M 10/25/2021
这个问题是关于什么回报;不是程序可以完成的其他方式main
1赞 NinjaDarth 8/19/2022 #19

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

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

至于返回什么的问题:C 和 C++ 的 ISO 标准与 POSIX 标准同步工作。对于任何符合 POSIX 标准的托管环境,(1) 126 保留给操作系统的 shell 以指示不可执行的实用程序,(2) 127 保留给操作系统的 shell 以指示未找到的命令,(3) 实用程序的退出值在逐个实用程序的基础上单独说明,(4) 调用 shell 外部实用程序的程序应使用类似的值作为自己的出口,




(5) 值 128 及以上用于指示接收信号导致的终止,(6) 值 1-125 表示失败,

(7) 值 0 表示成功。

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

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

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

对于用于独立环境或不符合 POSIX 的主机的程序,我只能说,除了以下内容:

我编写了独立程序 - 作为自定义运行时系统上的多线程程序(以及其他所有内容的自定义工具库)。我遵循的一般规则是:
(1)“main”运行前台进程,通常只包括启动、配置或初始化例程,但也可以包括用于持续操作的前台进程(如轮询循环),
(2)“main”返回到无限的睡眠和等待循环中,(3)没有定义或使用“main”的返回值,

(4)后台进程作为中断驱动和事件驱动的线程单独运行,独立于“主”,仅通过接收到复位信号或其他线程终止。或者简单地关闭对驱动线程的任何事件的监视。