main 函数的返回值大家注意了吗?也许有人会说可以没有返回值,如果你深入到程序的 CRT启动代码,你会发现....
1 __initenv = envp; 2 mainret = main(argc, argv, envp); 3 if ( !managedapp ) 4 exit(mainret); 5 if (has_cctor == 0) 6 cexit();
按照新的 C99 标准,即使函数本身没有定义返回值,编译器也会加上,以返回给激发程序运行状态。很多人甚至市面上的一些书籍,都使用了 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 和 int main
很多C程序员都曾搞错的一个概念,就是以为这样一个函数不接受任何参数:int foo();事实上,这个函数被认为可以接受未知个数的参数(译:可接受任意多的参数!)。正确的用法是在括号内添加关键字void。
在 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。总而言之:void main 主函数没有返回值,main 默认为 int 型,即 int main(), 返回整数。注意,新标准不允许使用默认返回值,即 int 不能省,而且对应 main 函数不再支持 void 型返回值,因此为了使程序有很好的移植性,强烈建议使用:
int main()
{
return 0; /* 新标准主函数的返回值这条语句可以省略 */
}
返回值的作用: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。
最后,关于为什么void main(void)是一种错误的用法,这里附上一篇更细节化的英文文章:
void main(void) - the Wrong ThingThe 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:
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. |