main() 在 C 和 C++ 中应该返回啥?
Posted
技术标签:
【中文标题】main() 在 C 和 C++ 中应该返回啥?【英文标题】:What should main() return in C and C++?main() 在 C 和 C++ 中应该返回什么? 【发布时间】:2021-07-23 23:51:06 【问题描述】:在 C 和 C++ 中定义 main()
函数的正确(最有效)方法是什么——int main()
或 void main()
——为什么?论点又如何?
如果int main()
那么return 1
或return 0
?
这个问题有很多重复,包括:
What are the valid signatures for C'smain()
function?
The return type of main()
function
Difference between void main()
and int main()
?
main()
's signature in C++
What is the proper declaration of main()
? — 对于 C++,确实有很好的答案。
Styles of main()
functions in C
Return type of main()
method in C
int main()
vs void main()
in C
相关:
C++ —int main(int argc, char **argv)
C++ — int main(int argc, char *argv[])
Is char *envp[]
as a third argument to main()
portable?
Must the int main()
function return a value in all compilers?
Why is the type of the main()
function in C and C++ left to the user to define?
Why does int main()
compile?
Legal definitions of main()
in C++14?
【问题讨论】:
我仍然认为它也相当模糊。为我定义“最有效”。在什么意义上有效?在占用更少内存的意义上?在跑得更快的意义上?我可以看到有用的答案,但我仍然认为这个问题的措辞很糟糕。 Pish posh,高效的上下文在这里很明显,尤其是示例(这些示例可能会阐明“高效”的定义)。希望可怜的缓冲区没有爬进洞里,完全后悔这个问题。可以说,无论是 void 还是 int,都会返回一个值,因此它不会影响文件大小、执行的操作或分配的内存。而且,在大多数操作系统中,人们倾向于在成功时返回 0,而在其他成功或失败时返回其他值——但没有标准。最终,效率上没有任何明显的差异。 “正确(最有效)”没有意义。高效是一回事,正确是另一回事。main
被调用一次(在 C++ 中只能被调用一次:没有递归)。如果不希望执行在main
中花费大量时间,那么不要多次调用程序:让程序实现重复。
我觉得有趣的是,据我所知,没有一个答案提供了一个完整的示例,包括 #include
语句
返回值在没有操作系统的平台上没有意义。你不会回到任何东西。如果您在嵌入式设备上点击main(...)
中的return
,您的系统将进入不可预知的状态,您的洗衣机将变得具有自我意识并试图杀死您。所以,在这种情况下,我们使用void main()
。这是裸机嵌入式的行业标准做法。
【参考方案1】:
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 0 和非零是正确的,但对于阅读您的代码的人来说完全没有意义。这个问题证明人们不知道什么是有效/无效代码。 EXIT_SUCCESS/EXIT_FAILURE 更加清晰。【参考方案2】:接受的答案似乎是针对 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 个严格符合的返回值(即不依赖于实现定义的行为):0
和 EXIT_SUCCESS
用于成功终止,EXIT_FAILURE
用于不成功终止。任何其他值都是非标准的和实现定义的。在 C90 中,main()
必须在末尾有一个显式的 return
语句以避免未定义的行为。在 C99 和更高版本中,您可以省略来自 main()
的 return 语句。如果你这样做了,并且main()
完成了,就会有一个隐含的return 0
。
最后,从标准的角度来看,从 C 程序调用 main()
递归 没有任何问题。
【讨论】:
@Lundin 我认为您不需要引用来说明允许某人制作接受不符合标准的程序的编译器,或者拥有不符合标准的编译器。这是常识和常识 @KABoissonneault 实现定义的行为是标准中的一个术语,与完全未记录的行为相反。如果你实现了一些被列为实现定义行为的东西,你仍然遵循标准。在这种情况下,引用的 C89 没有列出此类实现定义的行为,因此需要引用,以证明他不是凭空捏造的。 @Lundin 你看错了。我们谈论的不是实现定义的行为,我们谈论的是偏离标准的实现,如果他们选择这样做的话。这更像是一个孩子不听父母的话:你不需要父母的一句话来告诉你孩子可以以什么方式违背父母的话。您只知道孩子选择这样做的那一刻,他们就不再遵守父母的准则 @KABoissonneault 我在评论中引用的部分绝对是关于实现定义的行为(与非标准编译器扩展相反)。因此我说的是实现定义的行为。如果您正在对其他事情进行独白,那么祝您好运。 @Lundin 我猜引用中的措辞令人困惑(他们说“但这使得程序实现定义”的部分)但我很确定这个人在谈论非标准行为(如“如果 实现 允许它”和“不再严格符合 [to the standard]”中所述),而不是实际实现定义的行为。该人绝对应该改写他们的答案,但我仍然认为没有必要引用标准【参考方案3】:标准 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]
inclusive 应包含指向字符串的指针,这些指针是给定的 在程序启动之前由主机环境实现定义的值。这 意图是向程序提供在程序启动之前确定的信息 来自托管环境中的其他地方。如果宿主环境不能 提供带有大写和小写字母的字符串,实现 应确保以小写形式接收字符串。 如果argc
的值大于零,则argv[0]
指向的字符串 代表程序名称;argv[0][0]
应为空字符,如果 程序名称在主机环境中不可用。如果argc
的值为 大于一,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
函数的初始调用相当于调用exit
函数的值 由main
函数作为其参数返回;11) 到达终止main
函数返回值 0。如果返回类型与int
不兼容,则 返回宿主环境的终止状态未指定。11) 按照6.2.4,
main
中声明的具有自动存储时长的对象的生命周期 将在前一种情况下结束,即使在后一种情况下他们不会结束。
请注意,0
被强制要求为“成功”。如果您愿意,可以使用 <stdlib.h>
中的 EXIT_FAILURE
和 EXIT_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 主函数[basic.start.main]
¶1 程序应包含一个名为 main 的全局函数,它是程序的指定开始。 [...]
¶2 实现不应预定义主要功能。该功能不得重载。它应 有一个 int 类型的返回类型,否则它的类型是实现定义的。 所有实现 应允许以下两个 main 定义:
int main() /* ... */
和
int main(int argc, char* argv[]) /* ... */
在后一种形式中,
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 节 开始和终止 段落与第 7.22.4.4 节 exit
函数 中的段落相同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)。
微软 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 的 versions Visual Studio 2008 及以上。
标准 C — 独立环境
如前所述,上述要求适用于托管环境。如果您使用的是独立环境(这是托管环境的替代方案),那么标准就没有什么可说的了。对于独立环境,程序启动时调用的函数不需要调用main
,并且对其返回类型没有限制。标准说:
5.1.2 执行环境
定义了两种执行环境:独立和托管。在这两种情况下, 当执行调用指定的 C 函数时,程序启动发生 环境。所有具有静态存储持续时间的对象都应在程序启动之前进行初始化(设置为其初始值)。这种初始化的方式和时间是未指定的。程序终止将控制权返回给执行环境。
5.1.2.1 独立环境
在独立环境中(C 程序的执行可能在没有任何操作系统优势的情况下发生),程序启动时调用的函数的名称和类型是实现定义的。除了第 4 条要求的最小集合之外,独立程序可用的任何库设施都是实现定义的。
独立环境中程序终止的效果是由实现定义的。
第 4 条一致性的交叉引用指的是:
¶5严格符合程序应仅使用本国际标准中指定的语言和库的那些功能。3)它不应产生依赖于任何未指定的输出、未定义或实现定义的行为,并且不得超过任何最小实现限制。
¶6 两种形式的一致性实现是hosted 和freestanding。 符合要求的托管实现应接受任何严格符合要求的程序。 一致的独立实现应接受任何严格一致的程序,其中库条款(第 7 条)中指定的功能的使用仅限于标准头文件
<float.h>
、<iso646.h>
的内容,<limits.h>
,<stdalign.h>
,<stdarg.h>
、<stdbool.h>
、<stddef.h>
、<stdint.h>
和<stdnoreturn.h>
。符合要求的实现可能有扩展(包括 额外的库函数),前提是它们不会改变任何严格遵守程序的行为。4)¶7 符合标准的程序是符合标准的实现可接受的程序。5)
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 中所示的标题。
提供的标头
<cstdlib>
版本应至少声明函数abort
、atexit
、at_quick_exit
、exit
和quick_exit
(18.5)。此表中列出的其他标头应满足与托管实现相同的要求。表 16 - 独立实现的 C++ 头文件
Subclause Header(s) <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
和_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;
【讨论】:
@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,这个标题比问答内容更宏大。【参考方案4】:
我相信main()
应该返回EXIT_SUCCESS
或EXIT_FAILURE
。它们定义在stdlib.h
【讨论】:
@ChrisYoung 有EXIT_SUCCESS
和EXIT_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兼容的类型,那么从初始调用主函数返回就相当于用返回值调用退出函数由 main 函数作为其参数;11) 到达终止 main 函数的 返回值 0。"
然后是 7.22.4.4。关于退出函数:“如果status的值为0或EXIT_SUCCESS,则返回状态成功终止的实现定义形式。如果status的值为EXIT_FAILURE,则返回状态不成功终止的实现定义形式。否则返回的状态是实现定义的。”【参考方案5】:
请注意,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 程序终止
如果主函数的返回类型是与int兼容的类型,则从初始调用主函数返回相当于以主函数返回的值作为参数调用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,pointer to pointer to 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,句号。因为当他制作第一个 C++ 版本时,他只习惯于托管系统。在链接的帖子中,Stroustrup仍然似乎没有注意到独立实现的存在:例如,他无知地引用了 C 标准的托管实现子章,而忽略了第 5.1.2.1 章的存在。
C11 标准草案值得注意的是,尽管func()
被认为已过时,但草案本身在其示例中使用了int main()
。【参考方案6】:
成功返回 0,错误返回非零。这是 UNIX 和 DOS 脚本用来查明您的程序发生了什么的标准。
【讨论】:
【参考方案7】:main()
在 C89 和 K&R C 中未指定的返回类型默认为 '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 之后的版本中,在 C++ 和 C99 之后,而不是在 C90 中,当 main “落到尽头”时自动返回 0。
@Kaz 是的,我已经相应地更新了答案,实际上我已经问过这个问题***.com/questions/8677672/…【参考方案8】:
请记住,即使您返回的是 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 位整数。【参考方案9】:
操作系统可以使用返回值来检查程序是如何关闭的。
返回值 0 通常在大多数操作系统(我能想到的那些)中表示 OK。
也可以在你自己调用进程时检查,看看程序是否正常退出和结束。
这不只是一个编程约定。
【讨论】:
问题中没有任何内容表明存在操作系统。在独立系统中返回值没有任何意义。【参考方案10】:main()
的返回值显示了程序是如何退出的。如果返回值为zero
,则表示执行成功,而任何非零值都表示执行中出现问题。
【讨论】:
这是评论,不是问题的答案。【参考方案11】:返回 0 应该告诉程序员程序已经成功完成了工作。
【讨论】:
从main()
返回 1 通常表示发生了错误;返回 0 表示成功。如果你的程序总是失败,那么 1 是可以的,但这不是最好的主意。
@JonathanLeffler:从main
返回1
的含义是实现定义的。唯一的语言定义值是0
、EXIT_SUCCESS
(通常定义为0
)和EXIT_FAILURE
。在 OpenVMS 中,return 1;
表示 成功 终止。
VMS 不是“正常的”——正如我所说的那样。这难道不是类似于“任何奇怪的价值都是成功吗?偶数值在 VMS 上都失败了?【参考方案12】:
省略return 0
当 C 或 C++ 程序到达 main
的末尾时,编译器会自动生成返回 0 的代码,因此无需将 return 0;
显式放在 main
的末尾。
注意:当我提出这个建议时,几乎总是跟着两种 cmets 之一:“我不知道。”或“这是个坏建议!”我的理由是,依赖标准明确支持的编译器行为是安全且有用的。对于 C,从 C99 开始;参见 ISO/IEC 9899:1999 第 5.1.2.2.3 节:
[...] 对
main
函数的初始调用的返回等效于以main
函数返回的值作为参数调用exit
函数;到达终止main
函数的返回值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 上给出这个建议的人得到一个 *** 答案,我们可以指出关于省略 @987654338 的做法@ 这是一个糟糕的建议,因为仅实现 C89 而不是任何后续标准的编译器仍然非常普遍(我在 2017 年写了这篇文章)并且在可预见的情况下仍然非常普遍未来。例如,上次我检查了 no 版本的 Microsoft 编译器实现了 C99,据我了解,这对于非 GCC 的嵌入式系统编译器仍然很典型。 @zwol:任何别无选择,只能使用已过时 28 年的编译器的人可能比决定是否明确包含return 0;
有更多问题,但我会注意到许多编译器那个时代甚至在标准化之前也实现了隐式return 0;
。
你说的是真的。我的意思只是为“糟糕的建议”反应提供一个理由,而不仅仅是“它看起来很奇怪”。
实际上,我做了很多嵌入式系统的工作,十多年来还没有遇到过不支持隐式return 0
的编译器。也是当前版本的 Microsoft C support it as well。也许您的信息已经过时了?【参考方案13】:
返回什么取决于你想对可执行文件做什么。例如,如果您将程序与命令行 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 中它可以用来创建分支。【参考方案14】:
如果您确实遇到与从进程返回整数的效率相关的问题,您可能应该避免多次调用该进程以致该返回值成为问题。
如果你这样做(多次调用一个进程),你应该找到一种方法将你的逻辑直接放在调用者中,或者放在一个 DLL 文件中,而不为每个调用分配一个特定的进程;在这种情况下,多进程分配给您带来了相关的效率问题。
详细地说,如果您只想知道返回 0 是否比返回 1 更有效或更低,在某些情况下它可能取决于编译器,但一般来说,假设它们是从相同的源(本地、字段、常量,嵌入在代码中,函数结果等)它需要完全相同的时钟周期数。
【讨论】:
【参考方案15】:这里有一个返回码使用的小示范……
在使用 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
和-q
到grep
来防止出现匹配或常规错误消息。然而,这是 shell 的细节——关键点,退出状态很有用——没问题。【参考方案16】:
在 C 和 C++ 中定义 main() 函数的正确(最有效)方法是什么 - int main() 或 void main() - 为什么?
“(最有效)”这些词不会改变问题。除非您处于独立环境中,否则有一种普遍正确的方式来声明 main()
,那就是返回 int。
main()
在 C 和 C++ 中应该返回什么?
这不是 应该 main()
返回的,而是 应该 main()
返回的。 main()
当然是别人调用的函数。您无法控制调用main()
的代码。因此,您必须使用类型正确的签名声明 main()
以匹配其调用者。你在这件事上根本没有任何选择。您不必问自己什么效率更高或更低,或风格更好或更差,或类似的事情,因为答案已经由 C 和 C+ 标准完美定义。跟着他们就行了。
如果 int main() 则返回 1 还是返回 0?
0 表示成功,非零表示失败。同样,不是您需要(或获得)选择的东西:它是由您应该遵守的接口定义的。
【讨论】:
【参考方案17】:在 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),则“返回到主机环境的状态未指定”(即大多数操作系统上的未定义行为)。
【讨论】:
【参考方案18】:在 Windows 上,如果程序因访问冲突而崩溃,退出代码将为 STATUS_ACCESS_VIOLATION (0xC0000005)
。 x86 异常导致的其他类型的崩溃也类似。
因此,除了您从main
返回或传递给exit
的内容之外,还有其他内容可能会导致看到退出代码。
【讨论】:
这个问题是关于main
返回什么;不是程序可以完成的其他方式
投删除票,因为此答案不会尝试回答问题。以上是关于main() 在 C 和 C++ 中应该返回啥?的主要内容,如果未能解决你的问题,请参考以下文章
请问在C程序中啥时候用int main() 啥时候用 void main()。二者是不是可以相互代替?