关于C/C++中主函数的声明及返回值问题

前言

  在C/C++程序中有时会见到采用诸如void main()等方式来声明主函数,很多人在主函数返回值的问题上也略显随意。经过查阅多版本的C、C++标准文档与相关书籍,我将我的结论与认识总结于此。不可避免地,其中可能包含我错误的认识,欢迎大家指正。

main()函数的函数类型

关于main()的原型,C89/99/11以及C++98/03/11/14等标准给出的说法略有出入,尤其在早期标准中差异更为显著。然而,共同的,在任何一个版本的标准中,均未承认void main()这种声明方式。首先可以得出结论:void main()为错误写法,所有编译器都没有理由支持该种写法。

在K&R C与C89里,若函数没有显式声明返回类型,则默认是int,因此在早期C程序中经常出现这样的主函数:

1
2
3
4
5
main()
{
// 函数体
return 0;
}

这种写法在早期标准中是被认可的,main()等价于int main()。但是,C99标准不再支持函数的int类型的默认设置,因此该写法被废止,新的编译器不再允许此类main函数。

在C99/11标准中,明确定义了对于标准的main函数的两个原型:

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

为了增强说服力,我在《C Primer Plus》(第六版)(该书以1999 ISO/ANSI作为标准)中找到了这样一段话:

  如果浏览老版本的C代码,您将发现程序常常以:main()这种形式开始。C90标准勉强允许这种形式,但是C99标准不允许。因此即使您当前的编译器允许,也不要这么做。
  您还将看到另一种形式:void main()
  有些编译器允许这种形式,但是还没有任何标准考虑接受它。因而,编译器不必接受这种形式,并且许多编译器也不这样做。再者说,如果坚持使用标准形式,那么当您把程序从一个编译器移到另一个编译器时也不会有问题。

main()函数的形式参数

此部分C与C++略有差异

  • C

int main()和int main(void)在C语言中是有区别的:

1
2
3
int main() { /* ... */ }
// 不等价于
int main(void) { /* ... */ }

在C语言中参数列表为空(即不提供参数列表也不为void),表示不提供参数数量和参数类型信息:

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>

void func() // 参数列表为空不代表不接受参数
{
print("Hello, world\n");
}
int main(void)
{
func(1,2,3,4); // 调用func(),并传递了参数
return 0;
}

因此在C中,main函数的参数部分应该以以下两种方式描述:

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

不应该写:

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

  • C++

由于C和C++中对于函数参数列表的规则并不一致(C++中参数列表为空代表不接收任何参数)。所以C++中main的原型和ISO C也并不太一样。
在C++中,下面两种写法是完全等价的。

1
2
3
int main() { /* ... */ }
// 等价于
int main(void) { /* ... */ }

以上两种形式均符合标准。

main()函数的返回值

上文指出,main()应以int类型定义,故需要返回一个int类型的返回值到操作系统。
在早期标准中,主函数中的return 0;是可以省略的,main()最后如果没有返回值,则会自动return 0;,但是记住,只有main函数是这样,其他函数不能省略return
自C99/C++98以来,主函数的返回值为强制,若漏掉了返回语句,大多数编译器会给出警告,但仍将编译程序。

  • return 0;:一般用在主函数结束时,按照程序开发的一般惯例,表示成功完成本函数
  • return -1;:一般用在子函数结尾,按照程序开发的一般惯例,表示该函数失败

main()必须要有返回值的原因是:在C和C++中使用return-statement都是将return的值作为参数来调用exit/std::exit来终止程序。
此外,返回值对于某些操作系统(包括DOS和UNIX)而言,具有实际的用途。
以Windows为例,编译以下程序:

1
2
3
4
5
/* test.c */
int main(void)
{
return 0;
}

在命令提示符下运行命令test && dir,得到以下结果:

1
2
3
4
5
6
7
8
9
10
11
12
D:\code>test && dir

D:\code 的目录

2018/09/26 10:31 <DIR> .
2018/09/26 10:31 <DIR> ..
2018/09/26 10:29 35 test.c
2018/09/26 10:30 41,664 test.exe
2 个文件 41,699 字节
2 个目录 605,409,882,112 可用字节

D:\code>

test.exe正常结束后执行dir指令,输出了目录列表。
若将第4行返回值由0改为-1:

1
return -1;

再次执行test && dir命令:

1
2
3
D:\code>test && dir

D:\code>

test.exe将-1返回给了操作系统,即程序异常结束,因此windows没有继续执行dir打印目录列表。
总结一下,正常情况下main函数应以return 0;结束,不能随意更改返回值。

结语

综上所述,一般情况下,C/C++程序的主函数都应该以如下方法编写:

1
2
3
4
5
int main(void)    // void在C++中可省,C不建议省
{
// 函数体
return 0;
}

即使其他不标准的声明方法也许不会影响程序的正常编译、运行,但为了程序的可移植性与易维护性,我们应该养成良好的编码习惯,利人利己。

参考文献
[1] Stephen Prata 编著,《C Primer Plus》,人民邮电出版社,2005年,§2.2 §9.2 §B.5.
[2] Andrew Koenig 编著,《C陷阱与缺陷》,人民邮电出版社,2008年,§2.1 §7.1.
[3] Stephen Prata 编著,《C++ Primer Plus》,人民邮电出版社,2012年,§2.1 §2.4.