void main(void) – the Wrong Thing
很多人甚至市面上的一些书籍,都使用了void main( ) ,其实这是错误的,C/C++ 中从来没有定义过void main( ) 。
C++ 之父 Bjarne Stroustrup 在他的主页上的 FAQ 中明确地写着 The definition void main( ) { /* ... */ } is not and never has been C++, nor has it even been C.
void main( ) 从来就不存在于 C++ 或者 C
下面我分别说一下 C 和 C++ 标准中对 main 函数的定义。
1. C
在 C89 中,main( ) 是可以接受的。Brian W. Kernighan 和 Dennis M. Ritchie 的经典巨著 The C programming Language 2e(《C 程序设计语言第二版》)用的就是 main( )。不过在最新的 C99 标准中,只有以下两种定义方式是正确的:
int main( void )
int main( int argc, char *argv[] )
(参考资料:ISO/IEC 9899:1999 (E) Programming languages — C 5.1.2.2.1 Program startup)
当然,我们也可以做一点小小的改动。例如:char *argv[] 可以写成 char **argv;argv 和 argc 可以改成别的变量名(如 intval 和 charval),不过一定要符合变量的命名规则。
如果不需要从命令行中获取参数,请用int main(void) ;否则请用int main( int argc, char *argv[] ) 。
main 函数的返回值类型必须是 int ,这样返回值才能传递给程序的激活者(如操作系统)。
如果 main 函数的最后没有写 return 语句的话,C99 规定编译器要自动在生成的目标文件中(如 exe 文件)加入return 0; ,表示程序正常退出。不过,我还是建议你最好在main函数的最后加上return 语句,虽然没有这个必要,但这是一个好的习惯。注意,vc6不会在目标文件中加入return 0; ,大概是因为 vc6 是 98 年的产品,所以才不支持这个特性。现在明白我为什么建议你最好加上 return 语句了吧!不过,gcc3.2(Linux 下的 C 编译器)会在生成的目标文件中加入 return 0;
2. C++
C++98 中定义了如下两种 main 函数的定义方式:
int main( )
int main( int argc, char *argv[] )
(参考资料:ISO/IEC 14882(1998-9-01)Programming languages — C++ 3.6 Start and termination)
int main( ) 等同于 C99 中的 int main( void ) ;int main( int argc, char *argv[] ) 的用法也和 C99 中定义的一样。同样,main 函数的返回值类型也必须是int。如果main函数的末尾没写return语句,C++98 规定编译器要自动在生成的目标文件中加入 return 0; 。同样,vc6 也不支持这个特性,但是 g++3.2(Linux 下的 C++ 编译器)支持。
3. 关于 void main
在 C 和 C++ 中,不接收任何参数也不返回任何信息的函数原型为“void foo(void);”。可能正是因为这个,所以很多人都误认为如果不需要程序返回值时可以把main函数定义成void main(void) 。然而这是错误的!main 函数的返回值应该定义为 int 类型,C 和 C++ 标准中都是这样规定的。虽然在一些编译器中,void main 可以通过编译(如 vc6),但并非所有编译器都支持 void main ,因为标准中从来没有定义过 void main 。g++3.2 中如果 main 函数的返回值不是 int 类型,就根本通不过编译。而 gcc3.2 则会发出警告。所以,如果你想你的程序拥有很好的可移植性,请一定要用 int main 。
4. 返回值的作用
main 函数的返回值用于说明程序的退出状态。如果返回 0,则代表程序正常退出,否则代表程序异常退出。下面我们在 winxp 环境下做一个小实验。首先编译下面的程序:
int main( void ) { return 0; }
然后打开附件里的“命令提示符”,在命令行里运行刚才编译好的可执行文件,然后输入“echo %ERRORLEVEL%”,回车,就可以看到程序的返回值为 0 。
假设刚才编译好的文件是 a.exe ,如果输入“a && dir”,则会列出当前目录下的文件夹和文件。但是如果改成“return -1”,或者别的非 0 值,重新编译后输入“a && dir”,则 dir 不会执行。因为 && 的含义是:如果 && 前面的程序正常退出,则继续执行 && 后面的程序,否则不执行。也就是说,利用程序的返回值,我们可以控制要不要执行下一个程序。这就是 int main 的好处。如果你有兴趣,也可以把 main 函数的返回值类型改成非 int 类型(如 float),重新编译后执行“a && dir”,看看会出现什么情况,想想为什么会出现那样的情况。顺便提一下,如果输入 a || dir 的话,则表示如果 a 异常退出,则执行 dir 。
5. 那么 int main( int argc, char *argv[], char *envp[] ) 呢?
这当然也不是标准 C 里面定义的东西!char *envp[] 是某些编译器提供的扩展功能,用于获取系统的环境变量。
因为不是标准,所以并非所有编译器都支持,故而移植性差,不推荐使用。
Q: 我可以写"void main()"吗?
A: 这样的定义
void main() { /* ... */ }
不是C++,也不是C。(参见ISO C++ 标准 3.6.1[2] 或 ISO C 标准 5.1.2.2.1) 一个遵从标准的编译器实作应该接受
int main() { /* ... */ }
和
int main(int argc, char* argv[]) { /* ... */ }
编译器也可以提供main()的更多重载版本,不过它们都必须返回int,这个int是返回给你的程序的调用者的,这是种“负责”的做法,“什么都不返回”可不大好哦。如果你程序的调用者不支持用“返回值”来交流,这个值会被自动忽略——但这也不能使void main()成为合法的C++或C代码。即使你的编译器支持这种定义,最好也不要养成这种习惯——否则你可能被其他C/C++程序员认为浅薄无知哦。
在C++中,如果你嫌麻烦,可以不必显式地写出return语句。编译器会自动返回0。例如:
#include<iostream> int main() { std::cout << "This program returns the integer value 0/n"; }
麻烦吗?不麻烦,int main()比void main()还少了一个字母呢 :O)另外,还要请你注意:无论是ISO C++还是C99都不允许你省略返回类型。这也就是说,和C89及ARM C++[译注:指Margaret Ellis和Bjarne Stroustrup于1990年合著的《The Annotated C++ Reference Manual》中描述的C++]不同,int并不是缺省返回值类型。所以,
#include<iostream>
main() { /* ... */ }
会出错,因为main()函数缺少返回类型。
void main(void) - the Wrong Thing
The newsgroup, comp.lang.c, is plagued by an almost continuous discussion of whether we can or cannot use void as a return type for main. The ANSI standard says "no", which should be an end of it. However, a number of beginners' books on C have used void main(void) in all of their examples, leading to a huge number of people who don't know any better.
When people ask why using a void is wrong, (since it seems to work), the answer is usually one of the following:
Because the standard says so.
(To which the answer is usually of the form "but it works for me!")
Because the startup routines that call main could be assuming that the return value will be pushed onto the stack. If main() does not do this, then this could lead to stack corruption in the program's exit sequence, and cause it to crash.
(To which the answer is usually of the form "but it works for me!")
Because you are likely to return a random value to the invokation environment. This is bad, because if someone wants to check whether your program failed, or to call your program from a makefile, then they won't be able to guarantee that a non-zero return code implies failure.
(To which the answer is usually of the form "that's their problem").
This page demonstrates a system on which a void main(void) program will very likely cause problems in the third class above. Calling the program from a script may cause the script to die, whether or not its return code is checked. Calling it from a makefile may cause make to complain. Calling it from the command line may cause an error to be reported.
RISC OS is the native operating system of Acorn's range of ARM based computers. One of the facilities of this OS is a system variable, Sys$RCLimit. The value of this variable specifies the maximum value that a program may return to the OS without causing RISC OS itself to raise an error. The default value of this variable is set by the OS at 256. I'm not too sure what the intended function of this variable was, but it exists, and that's that.
Now, let's look at an example program using int main(void).
int main(void) { return 42; }
Compiling it to ARM assembly language, using gcc (as an aside: Acorn's own C compiler reports a warning with void main(void) and converts it to an integer function returning zero) gives the following:
|main|:
mov ip, sp
stmfd sp!, {rfp, fp, ip, lr, pc}
sub fp, ip, #4
cmps sp,sl
bllt |x$stack_overflow|
bl |___main|
mov r0, #42
ldmdb fp, {rfp, fp, sp, pc}^
The first six instructions are initialisation and stack checking. The final two return 42 to the library startup code. So, the return value of main is passed in R0. Note that the library startup code is expecting to call a function returning an integer, so will happily use the value returned in R0.
What happens with a void main function? Well, here's an example.
#include <stdio.h>
char buf[1024];
void main(void)
{
(void)fgets(buf, 1024, stdin);
}
The program waits for a line of text from its standard input, nothing else. Again we compile it to assembler:
|.LC0|:
dcd |__iob|
|.LC1|:
dcd |buf|
|main|:
mov ip, sp
stmfd sp!, {rfp, fp, ip, lr, pc}
sub fp, ip, #4
cmps sp,sl
bllt |x$stack_overflow|
bl |___main|
ldr r2, [pc, #|.LC0| - . - 8]
mov r1, #1024
ldr r0, [pc, #|.LC1| - . - 8]
bl |fgets|
ldmdb fp, {rfp, fp, sp, pc}^
area |buf|, DATA, COMMON, NOINIT
% 1024
Again, the first six instructions in main set things up. The next three set up the arguments for the call to fgets. Then we call fgets and return to the caller. stdio.h says that fgets returns a pointer to the buffer. So, in this instance, what we are returning to the library startup code is a pointer to buf. Under RISC OS, all C programs are mapped into memory at 0x8000. So, we will be returning a value to the OS which is > 32768 (hence, certainly > the default value of Sys$RCLimit). The OS then raises an error.
Here's the result of compiling and running the program:
SCSI: void % gcc void.c -o void
Drlink AOF Linker Version 0.28 30/07/95
SCSI: void % show Sys$RCLimit
Sys$RCLimit : 256
SCSI: void % void
I enter this line
Return code too large
SCSI: void %
And, in a script file:
SCSI: void % cat script
void
echo Finished
SCSI: void % run script
I enter this line
Return code too large
SCSI: void %
The error interrupts the script before the second command is run.
Note that the example above was a little contrived in order to make the final function call return a pointer. A better example where this could cause problems is one where the program uses printf to report a usage string > 256 characters long prior to returning or, worse still, one where the program uses printf to output data depending on user input. Depending on the length of the user's input text, the program may or may not cause an error which is solely due to the use of void as a return type for main.
So, if you want your software to be portable, please make main return int. It does matter.
参考推荐:
Bjarne Stroustrup 的 C++ 风格与技术 FAQ(中文版)
Bjarne Stroustrup's C++ Style and Technique FAQ (英文)
版权所有: 本文系米扑博客原创、转载、摘录,或修订后发表,最后更新于 2018-11-26 22:20:20
侵权处理: 本个人博客,不盈利,若侵犯了您的作品权,请联系博主删除,莫恶意,索钱财,感谢!