【C语言进阶剖析】33、main 函数与命令行参数

1 main 函数的概念

  • C 语言中 main 函数成为主函数
  • 一个 C 程序是从 main 函数开始执行

请看下面 main 函数定义正确码?
在这里插入图片描述
还是用编译器尝试一下
在这里插入图片描述
从结果我们可以看到除了 A,编译器给出警告,main 函数默认返回类型为 int,其他都编译运行没有问题。

也就是说上面四种写法都是正确的。

1.1 main 函数的本质

  • main 函数是操作系统调用的函数
  • 操作系统总是将 main 函数作为应用程序的开始
  • 操作系统将 main 函数的返回值作为程序的退出状态

操作系统是希望 main 函数的有返回值的,这样可以知道 main 函数的退出状态。如果程序时异常退出的,在有的系统上面,会得到提示:程序异常退出。main 函数的返回值正常来说是 0,如果是其他值,就是错误的状态。

思考:为什么 C 编译器支持这么多不同的 main 函数原型?

C 语言应用早期,Unix 系统刚刚诞生,C 语言的应用还比较简单,进行科学计算或者简单的嵌入式编程,不想现在有如此丰富的操作系统,程序不在操作系统上运行,返回值也没有什么意义,所以在这种情况下,干脆不写 main 函数法返回值了。到后期,出现很多商业编译器,比如 bcc,gcc,vs,只有最大的支持 C 语言的特性才能卖的好呀,不然很顶卖的不好呀。由于历史的原因和商业竞争的原因,gcc 编译器肯定也是支持不同的 main 函数原型的。

1.2 编程实验

这个实验我们来看一下 main 函数的返回值,这次在 window 下用 bcc 编译器编译。

// 33-2.c
#include <stdio.h>
int main()
{
    return 99;
}

这是最简单的函数,编译执行后可以通过环境变量 %ERRORLEVEL% 获得函数的返回值,我们尝试一下:
在这里插入图片描述
main 函数执行结束后将返回值返回给操作系统,我们执行命令 echo %ERRORLEVEL% 可以获得这个返回值。

现在有个问题,操作系统拿这个返回值做什么呢?,我们继续尝试

// 33-2-A.c
#include <stdio.h>
int main()
{
	printf("I'm A!\n");
    return 0;
}
// 33-2-B.c
#include <stdio.h>
int main()
{
	printf("I'm B!\n");
    return 99;
}

在这里插入图片描述

  • 对于上面的结果,对 33-2-A.c 和 33-2-B.c 分别编译,运行,通过 echo %ERRORLEVEL% 查看 main 函数返回给操作系统的值,分别是 0 和 99。
  • 运行 33-2-B.exe && 33-2-A.exe,结果打印的是 I’m B!,说明 33-2-A.exe 没有运行,因为 33-2-B.exe 返回 99,&& 运算短路。运行 33-2-A.exe && 33-2-B.exe,没有短路。

操作系统有自己的批处理语言,上面我们写的 33-2-A.exe && 33-2-B.exe 就可以看作是一种批处理,在 Linux 下就是 shell 脚本。在批处理中我们就可以把程序看作黑盒,将返回值判断函数执行的状态。

2 main 函数的参数

  • 程序执行时可以向 main 函数传递参数

在这里插入图片描述
gcc 编译器的常见用法如下:参数一一对应
在这里插入图片描述

2.1 实例分析

// 33-3.c
#include<stdio.h>
int main(int argc, char* argv[], char* env[])
{
    int i = 0;
    printf("============= Begin argv ===========\n");
    for (i = 0; i < argc ;i++)
    {
        printf("%s\n", argv[i]);
    }
    printf("============= End argv ===========\n");
    printf("\n");
    printf("\n");
    printf("\n");
    printf("============= Begin env ===========\n");
    for (i = 0; env[i] != NULL; i++)
    {
        printf("%s\n", env[i]);
    }
    printf("============= Begin env ===========\n");
    return 0;
}

上面的代码,第 5 行打印命令行参数,第 18 行打印环境变量(循环结束条件是 env[i] != NULL)

编译运行结果如下:

$ gcc 33-3.c -o 33-3
$ ./33-3
============= Begin argv ===========
./33-3
============= End argv ===========



============= Begin env ===========
CLUTTER_IM_MODULE=xim
LS_COLORS=rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;

......

GTK_IM_MODULE=xim
LC_TIME=zh_CN.UTF-8
OLDPWD=/home/skx
_=./33-3
============= Begin env ===========

上面的结果由于环境变量参数太多,被我删除掉一些,这里我们不关心打印的结果。

将第 18 行注释,再次编译运行,结果如下:

$ gcc 33-3.c -o 33-3
skx@ubuntu:~/c$ ./33-3 a.c b.c c.c d.c
============= Begin argv ===========
./33-3
a.c
b.c
c.c
d.c
============= End argv ===========



============= Begin env ===========
============= Begin env ===========

可以看到命令行参数被一一打印出来

我们平时使用 Linux 下的各种命令都会有各种参数,这些参数就是命令行参数,main 函数的参数,根据我们命令行所给的参数做出不同的行为,这就是 main 函数参数的意义。

2.2 main 函数一定是程序执行的第一个函数吗?

这是一个开放性问题,不能简单的回答是或者不是,有些编译器(比如 gcc)是支持 main 函数之前执行函数的,有些编译器不支持。

下面我们说一下 gcc 中的一个属性关键字,直接看代码

// 33-4.c
#include<stdio.h>
#ifndef __GNUC__				// 如果是gcc编译器,就定义宏__attribute__(x)
#define __attribute__(x)
#endif

__attribute__((constructor))	// constructor告诉编译器,下面的函数将在main函数之前运行
void before_main()
{
    printf("%s\n", __FUNCTION__);// 宏__FUNCTION__将打印当前函数名
}
__attribute__((destructor))		// destructor告诉编译器,下面的函数将在main函数之后运行
void after_main()
{
    printf("%s\n", __FUNCTION__);
}
int main()
{
    printf("%s\n", __FUNCTION__);
    return 0;
}

第 3-5 行表示,如果是 gcc 编译器,将定义宏 __attribute__,如果不是 gcc 编译器,将删除 3-5 行的代码;第 7 行宏参数 constructor 表示下面的函数将在 main 函数之前执行,第 12 行的宏参数 destructor 表示下面的函数将在 main 函数之后执行。

下面编译运行一下试试,结果如下:

$ gcc 33-4.c -o 33-4
$ ./33-4
before_main
main
after_main

确实有函数可以在 main 函数之后执行。同样的程序在 window 下面用 vs 编译器编译。
在这里插入图片描述
可以看到只打印了 main。因为不是使用 gcc 编译器,源程序中第 3-5 行,第 7 行,第 12 行将被删除,程序变为下面的样子

// 33-4.c
#include<stdio.h>
void before_main()
{
    printf("%s\n", __FUNCTION__);// 宏__FUNCTION__将打印当前函数名
}
void after_main()
{
    printf("%s\n", __FUNCTION__);
}
int main()
{
    printf("%s\n", __FUNCTION__);
    return 0;
}

所以当然不会执行函数 before_main() 和函数 after_main() 了,只会执行 main 函数。

除了标准 C 语言之外,编译器还会支持自己的属性,有了 gcc 属性关键字,gcc 编译器就支持在 main 函数之前,之后执行别的函数,我们在项目中尽量不要编写编译器相关的代码,这样代码的可移植性才会高,不然换一个编译器就不能用了,这样的产品显然不太好。

3 小结

1、一个 C 程序是从 main 函数开始执行的
2、main 函数是操作系统调用的函数
3、main 函数有参数和返回值(操作系统根据 main 返回值判断程序执行状态,一般返回 0 为正常)
4、现代编译器支持在 main 函数前调用其他函数

发布了248 篇原创文章 · 获赞 115 · 访问量 9万+

猜你喜欢

转载自blog.csdn.net/happyjacob/article/details/103412866