main函数即为主函数,C程序总是从main()函数开始执行的。main函数是整个C程序的执行入口,一个程序想要运行起来,必须指定main函数。各种资料和书籍对main函数的写法各有不同,甚至很多的写法都有误区,本文整理了常见的mian函数写法,并逐一测试说明。
注意:以下程序均在GCC9.2环境下编译运行,不同的编译器编译结果并不一定相同。
标准版本
C89/C99/C11标准文档中只提供了两种main函数的写法:
int main(void) { /* ... */ }
int main(int argc, char *argv[]) { /* ... */ }
C99规定:main函数必须有返回值,而且必须是int 类型的,main函数return 0表示函数正常退出,否则表示错误退出,如果没有return语句,编译器自动在目标上添加return 0。
如果把main函数当成普通函数那么除了函数名main不可变,那么其他都有可以变,返回值类型,参数列表,返回值这三种搭配会产生各种形式。
无参main函数
样式一:int main(void){/* ... */}
样式二:void main(void){/* ... */}
样式二:main(void){/* ... */}
样式一不必多说,那么其他两个是怎么出现的?样式二这种常见于老旧的教材,我记忆中那会谭浩强的书里就有这种写法。目前不少的编译器是能够编译通过的,但是这是编译器的处理并不是标准中的定义,甚至在C99之后明确规定这种就是错误写法!样式三不写返回值类型在C89标准中允许,C语言第一版中只有int一种类型,没有char,long,float等,既然只有一种类型,那么就可以不写,后来的改进版为了兼容以前的代码便规定:不明确标明返回值的,默认返回值为int,也就是说样式三等同于样式二。虽然可以编译,但在C99标准中要求编译器至少给 main() 这种用法警告,强烈不建议这么写!
使用以上的形式输出Hello World结果如下:
样式一和二的结果:C89和C99下编译运行正常
样式三的结果:C89和C99下编译运行正常,但是C99给出警告
样式四:int main(){/* ... */}
样式五:void main(){/* ... */}
样式六:main(){/* ... */}
样式四到样式六对应着样式一到样式三,唯一不同在于里面的void省去了,很多人觉得这个应该和void一样的,都是表示不带参数的意思,但实际上并不是!在C语言中func()表示的是参数类型,数目不确定,并且可以重新对它的完整参数类型和个数重新声明。但是func(void)则完全限制了不允许任何参数,即使重新声明,也不能改变。
使用func():
void func(){
printf("func()\n");
}
int main(void){
func();
func(1,2);
func(1,'a');
func(1,"abc");
return 0;
}
func()任意参数列表都可以编译运行
使用func(void):
void func(void){
printf("func()\n");
}
int main(void){
func();
func(1,2);
func(1,'a');
func(1,"abc");
return 0;
}
func(void)只要有参数直接报错
使用样式四到样式六输出Hello World结果与样式一到样式三一致,图略。
有参main函数
样式一:int main(int argc, char *argv[]){/* ... */}
样式二:void main(int argc, char *argv[]){/* ... */}
样式三:main(int argc, char *argv[]){/* ... */}
样式四:int main(int argc){/* ... */}
样式五:void main(int argc){/* ... */}
样式六:main(int argc){/* ... */}
样式七:int main(int argc, char *argv[], char *envp[]){/* ... */}
样式八:void main(int argc, char *argv[], char *envp[]){/* ... */}
样式九:main(int argc, char *argv[], char *envp[]){/* ... */}
以上的各种形式中指针数组都可以使用二级指针替换:*argv[]->**argv,*envp[]->**envp.这里不一一列出来了。
标准的main函数中的参数最多有两个:第一个参数argc(argument count)是命令行中的字符串个数。第二个参数argv(argument value)是一个指针数组,一般程序名赋值给argv[0],从argv[1]开始对各个参数依次赋值。这里的参数可选,使用一个,使用两个都可以。
一个argc参数:
int main(int argc){
printf("%d",argc);
return 0;
}
带上程序名,一共四个参数
两个标准参数:
int main(int argc,char *argv[]){
int i;
for ( i = 0; i < argc; i++){
printf("%s|",argv[i]);
}
return 0;
}
输出所有的argv参数,这里的程序名包含路径
main函数还可以有第三个参数,用于获取环境变量,这种形式多源于编译器的扩展。但全局变量environ可以代替envp的作用,获取或者设置环境变量可以使用getenv或putenv,因此这种形式出现的很少。
三参数:
int main(int argc,char *argv[],char *envp[]){
int i;
for ( i = 0; i < argc; i++){
printf("%s|",argv[i]);
}
printf("\n");
int j=0;
while(envp[j]){
printf("%s\n",envp[j]);
j++;
}
return 0;
}
最后一个参数打印了全局环境变量
返回值
main函数的返回值最终会作为程序的退出状态,0表示函数正常退出,否则表示错误退出。这也是不推荐使用void作为返回值类型的原因,一旦声明为void,在程序退出后,想要获取其退出状态也就不可以了!如果main函数的最后没有写return语句的话,C99规定编译器要自动在生成的目标文件中加入return 0,表示程序正常退出。尽管省略return语句后,也能通过编译,但仍建议最好加上return语句,养成好习惯。
其他环境
在其他领域以上规则不尽然完全适用,单片机中使用void main(void)还是很常见的。很多低级单片机的资源有限,常见的比如51单片机,基本上不会使用系统的,一般就是裸跑, 编译器初始化后直接转到main运行,不会给main传任何参数。进入main里面做一些硬件初化后,一般会进入while的死循环保持运行,直到掉电,不需要返回值,所以经常可以看到void main(void)这样的代码。
51单片机点灯:
#include
sbit LED=P1^0;
void main(void){
LED=0;
while(1){
LED=1;
}
}