五花八门的main()
问:在各种C语言书上,能看到各式各样main()函数的写法,简直令人无所适从,这是这么回事?
答:原因主要有两个:一个是随着C语言的发展和演化,main()函数的写法也在不断变化;另外,某些书籍写法不规范或误导的现象也同时存在。
问:最初的main()的写法是什么样子?
答:最初main()函数的写法非常简洁,那个时候的C程序员哪怕一个字符似乎都不肯多写。不知道是因为当时键盘质量不好还是因为编辑器太糟糕的缘故,那个时代的C程序员似乎惊人地一致崇尚“简约”——甚至可以说是“至简”。
main()
{
printf("hello,world\n");
}
这就是main()函数最古老的写法,K&R在他们的经典名著《The C Programming Language》中的第一个C语言源程序(1978)。这种写法是那个时代的主流。
问:简直和裸体差不多,连#include<stdio.h>也没有么?
答:在《The C Programming Language》的第一版中确实没有。那个时代的C语言,返回值类型为int的函数不用声明。不过在该书的第二版(1988)中这个程序被改成了
#include <stdio.h>
main()
{
printf("hello,world\n");
}
问:返回值类型为int的函数不用声明的规则改变了吗?
答:规则没有改变。改变了的是观念,人们已经不再倾向于代码的“至简”,而开始倾向于在代码中交代清楚每一个标识符的来龙去脉。从C89开始倡导在函数调用之前一定要有函数声明,但并没有强求,而在C99这已经是强制性的要求了。
由于《The C Programming Language》第二版正值ANSI C标准颁布(1989)前夕出版,所以这种变化也应该视为ANSI C标准的倾向性以及K&R对新标准的认同。尽管这个例子没有完全反映出来这种认同。
问:为什么说没有完全反映出来这种认同呢?
答:因为这个main()的定义并没有按照函数原型(Function prototype)的方式来写,C90中规定不带参数的main()函数应该这样写:
int main(void) { /*. . .*/}
但同时规定那个int可以省略。C90把()内不写任何内容视为过时的写法,尽管C90无奈地容忍了它(The use of function declarators with empty parentheses (not prototype-format parameter type declarators) is an obsolescent feature.)。
问:为什么要容忍?
答:因为有许多老式的代码还在用。
问:如果以C99的标准看这个main()写得如何呢?
答:C99不容许省略int。但同样只把()内不写任何内容视为过时,而没有完全禁止,可见习惯力量的顽固。
问:那又为什么说K&R对新标准的认同呢?
答:《The C Programming Language》第二版中的其他函数定义和函数类型声明基本上都改成了函数原型风格。比如,在讲解main()函数的参数时,K&R把原来的main()函数
#include <stdio.h> main(argc,argv) int argc;char *argv[]; { /*…… */ return 0; }
改成了:
#include <stdio.h>
main(int argc,char*argv[])
{
/*…… */
return0;
}
前一个写法今天已经差不多绝迹,后一个main()以今天的眼光来看有些奇怪,main()的参数是用函数原型风格写的,但却没有写main()返回值的类型,给人有点半新半旧的感觉。尽管不能说它违背C90(因为C90容许不写main()前面的int),但如果写上了返回值的类型int,就同时满足现代C99标准的要求了。
问:这里出现的“return 0;”是怎么回事?
答:这在现代C语言中已经是司空见惯了,它返回给操作系统一个值以表明程序是在何种状态下结束的。
但在另一段代码中,K&R似乎又走得太远:
#include <stdio.h> main(int argc,char *argv[]) { int found = 0 ; /*……计算found的值 */ return found; }
这个实在有些“标新立异”,居然把计算结果返回给了操作系统,颇有突破常规之嫌。
问:那前面几个没有“return 0;”的main()函数会怎么样?
答:按照C90标准,会返回一个不确定的int类型的值,如果确实不关心这个返回值是多少,不写确实可以。
但C99却要求编译器在编译的时候帮忙给补上这个“return 0;”,C99在必须写int这个问题上没有迁就懒人,但在这里却对偷懒的做法给予了迁就。
问:如果确实不关心main()函数的返回值,把main()的返回值定义为void类型如何?我看到许多书上都这样写的。
#include <stdio.h> void main() { printf("This is a C program.\n"); }
答:这在C99之前是一种野路子写法,究竟从哪里冒出来的,无据可考。但前几年的主流教科书中这种写法很常见。K&R(C语言的发明者)没有这样写过,C90国际标准也不承认这种写法。Bjarne Stroustrup(C++语言的创始人)在他的关于C++的FAQ中,在回答是否可以写“void main()”时愤怒地回答说这种写法在C++和C中都不曾有过。事实上,很多C语言专家都认为“void main()”非常邪恶。
因此,在C99之前,这是不符合标准的写法。尽管这段代码的功能似乎是输出“This is a C program.”,但其实却不是一个“C program”。
问:但是有时这样写并没有产生错误啊?
答:首先,C语言的错误不一定反应在编译、链接或运行过程中。你输出一个垃圾值也可能一路通过编译、链接或运行,但这不说明你的代码没有错误,更不能说明这样的代码正确、有意义。
其次,这样的写法在有些编译器下程序会产生崩溃或得到警告。这说明这种写法至少不普遍性适用的。
可以说,如果不是C99标准,这种写法根本没有立锥之地。
问:C99给了这种写法以立足之地么?
答:从某种意义上也许可以这样理解。因为K&R没承认过这种写法,C90根本不承认这种写法,C99虽然没有正式承认这种写法,但为这种写法留了一个后门:“It shall be defined ……or in some other implementation-defined manner”。这意思就是说,如果编译器明确声称允许void main()这种写法的话,那么C99不再象C90那样简单认为这种写法违背C标准。
但是不管怎么说,这种写法最多是某些编译器的一种“方言土语”,如果没有特殊理由,比如仅仅是工作在某个特殊环境,且仅仅使用特定的编译器而根本不考虑程序的可移植性,为什么不写普遍适用的形式呢?
问:既然很多C语言专家都认为“void main()”非常邪恶,C99为什么包容这种写法呢?
答:很难确定C99是否就是打算专门想把这种写法也“收容”在标准之列。因为除了void main(),还有另外一些main()函数的写法被C90排除在标准之外了。而现在,这些写法在理论上也具备了符合C99标准的可能性。
问:还有什么样的main()函数?
答:很多编译器都支持下面的main()的写法
int main(int argc, char *argv[], char *env[]) { /* */ return 0; }
问:居然有3个形参,那个env是做什么用的?
答:那个参数可以使程序获得环境变量。
问:什么叫环境变量?
答:简单地讲可以理解为操作系统记录的一些数据,比如计算机的名字,操作系统放在哪里等等。应用程序在运行时可能要用到这些信息,这时可以通过env这个参数来获得。
问:如果编译器不支持main()的第三个参数怎么办?
答:标准库函数也可以达到同样的目的。
#include <stdlib.h> char *getenv(const char *name);
问:是否可以说void main()和int main(int argc, char *argv[], char *env[])也符合C99标准呢?
答:恐怕还不能这么说,现在只是不能说这两种写法一定不符合C99标准。但这两种写法不符合C90标准是确定的,这两种写法的可移植性很差也是确定无疑的。
C99标准在这里写的很模糊,没有进一步界定“implementation-defined manner”的含义。除非编译器声明遵守C99标准,且容许这两种写法,否则断言这两种写法符合C99标准属于空穴来风。
问:有人说“C99建议把main函数指定为int型”,这种说法对吗?
显然不对。因为C99并非绝对不包容返回值非int类型的main()。正确的说法是,C90要求main()函数的返回值一定得是int型。但C90容许不写那个int,而C99则要求必须写上这个“int”。
问:下面这种风格如何?
#include <stdio.h> int main() { printf("This is a C program.\n"); return 0; }
答:这个写法有点不伦不类。返回值的类型int写了,这个和C89的倡导或C99的要求一致,但是()里面什么都不写,又与标准的所倡导的风格不符,所以说不伦不类。
这种写法目前的标准依然容许,但属于标准目前尚能容忍的但即将过时的(obsolescent)写法,被抛弃只是早晚的问题。这种写法就如同古代的函数形参的写法一样
main(argc,argv) int argc;char *argv[]; { /*…… */ return 0; }
都属于历史的垃圾。
问:见过在main()的函数体的“}”之前前写一句
getch();
这个是怎么回事?
答:这个是时代的产物。在PC从DOS时代转变为Windows时代的过程中,DOS时代开发的IDE(主要是TC)无法在运行程序后显示输出结果,为了在运行后从容仔细地观察一下运行结果再返回IED界面,加上了这么一句,人为地延长程序运行时间(因为getch()会等待用户输入一个字符)。但这与main()本身的结构无关。这条语句不具备普遍意义,只是将就过时的IDE的一种权宜之计而已。。
所谓不具备普遍意义是指,第一,真正的程序往往不需要这条语句,就是说这条语句与程序功能无关;第二,getch()这个函数并不是标准函数,只有个别的编译器才支持它,在其他编译器上写这条语句,很可能行不通。
问:为什么不用getchar()这个标准库函数呢?
答:getchar()的功能和getch()有点区别,前者会在标准输出设备上显示用户键入的字符,这显得很不利索,而后者则不会显示用户所键入的字符,更接近“Press Any Key to Continue……”的效果。
问:有的代码在main()函数结束前写
system("PAUSE");
是否也是这个意思?
答:是的。这也是一种人工制造的“请按任意键继续. . .”,与程序功能结构无关,只是为了方便地观察输出结果。
但是这种写法比调用getch()要好,因为system()函数是标准库函数,各个编译器都提供支持。
问:有一种说法,“在最新的C99标准中,只有以下两种定义方式是正确的:”
int main( void ) { /* */ return 0; }
和
int main( int argc, char *argv[] ) { /* */ return 0; }
这种说法对吗?
答:这种说法显然不对。但可以确认的是这两种定义方式一定正确。不但在C99来说是正确的,以C89来说也是正确的。
问:还有一种写法
int main( void ) { return EXIT_SUCCESS; }
那个EXIT_SUCCESS是怎么回事
答:return EXIT_SUCCESS;是与return 0;等价的一种文雅的写法。
EXIT_SUCCESS是在stdlib.h中定义了的符号常量,返回这个值表示程序任务完成后程序退出。在stdlib.h定义的另一个符号常量EXIT_FAILURE,通常用于程序无法完成任务而退出。
问:实在太眼花缭乱了,我需要记住这么多吗?
答:显然没必要。很多东西都是历史原因遗留下的垃圾。
问:如果学习C语言,应该记住或使用哪种呢?
答:显然是
int main( void ) { /* */ return 0; }
和
int main( int argc, char *argv[] ) { /* */ return 0; }
第一,他们普遍适用,不存在可移植性的问题;
第二,就目前看,他们不存在任何过时或即将过时的成分。当然,如果喜欢文雅,不写return 0;而写EXIT_SUCCESS也可以。
顺便说一句,有的学习者记不住带参数main()函数两个形参的名字。其实这两个形参的名字也可以自己取,不一定用那两个名字,只要记住类型就可以了。第二个参数的类型也可以是char **,这和原来的是等价的。