天天看点

c并非所有的代码路径都返回值_深度理解:main函数的返回值main函数的前世今生main返回值到底有什么用?结论

最近碰到这样的一个问题:main函数的返回值的作用。

main函数的前世今生

每一个C程序都会有一个main函数作为入口点,这里所谓的入口点,也即程序一开始就会尝试找到main函数的位置,并从main函数的第一行代码开始执行。

那么,第一个问题:入口点之前是什么?main函数执行完之后,又是什么?这有点像:宇宙大爆炸之前的那一刻是什么状态?

我们来创建一个最简单的C程序,来看看入口点之前是什么?

如下图所示,我使用VS2010创建了一个控制台应用程序。

c并非所有的代码路径都返回值_深度理解:main函数的返回值main函数的前世今生main返回值到底有什么用?结论

代码十分简单,我们在main函数中打印了一句hello,然后返回了0。

接下来,我们在printf函数处设置一个断点,然后F5启动调试。程序如期的在printf处中断了下来,我们先来看看调用堆栈。

c并非所有的代码路径都返回值_深度理解:main函数的返回值main函数的前世今生main返回值到底有什么用?结论

在调用堆栈中,我们看到在main函数调用之前,有两次CRT库函数调用,具体代码如下:

c并非所有的代码路径都返回值_深度理解:main函数的返回值main函数的前世今生main返回值到底有什么用?结论
c并非所有的代码路径都返回值_深度理解:main函数的返回值main函数的前世今生main返回值到底有什么用?结论
c并非所有的代码路径都返回值_深度理解:main函数的返回值main函数的前世今生main返回值到底有什么用?结论

从以上观察得出结论:系统CRT的库中的wmainCRTStartup/mainCRTStartup为实际用户程序的入口点,我们自己编写的main函数由CRT调用,并且,在调用之前CRT会尝试对一些底层基础设施进行初始化,包括默认的异常处理函数表,各种底层中断的处理策略等。这些基础性的工作完成之后,CRT调用我们的main函数进入应用程序的实际代码中。

好了,看了大爆炸之前的那一刻,我们来研究一下main函数执行完毕后,会发生什么?

还是继续进行单步调试,看看main函数返回后,代码如何走:

c并非所有的代码路径都返回值_深度理解:main函数的返回值main函数的前世今生main返回值到底有什么用?结论
c并非所有的代码路径都返回值_深度理解:main函数的返回值main函数的前世今生main返回值到底有什么用?结论
c并非所有的代码路径都返回值_深度理解:main函数的返回值main函数的前世今生main返回值到底有什么用?结论
c并非所有的代码路径都返回值_深度理解:main函数的返回值main函数的前世今生main返回值到底有什么用?结论
c并非所有的代码路径都返回值_深度理解:main函数的返回值main函数的前世今生main返回值到底有什么用?结论

跟踪到最后,我们发现,CRT在收到main函数返回值后,将这个值传递了Win32API ExitProcess,当程序执行这个API时,操作系统将终止应用程序并释放整个进程资源,我们在main函数中准备的返回值,传递给了操作系统,操作系统会记录这个准备死亡的进程及其返回值,然后,进程死亡。

main返回值到底有什么用?

我们打开命令行窗口,分别让main返回0和77这两个不一样的值,然后使用%ERRORLEVEL%打印程序的退出代码,如下图:

c并非所有的代码路径都返回值_深度理解:main函数的返回值main函数的前世今生main返回值到底有什么用?结论

我们可以看到,main函数的返回值,被用作了程序的退出代码。如果您在Linux下做测试,可以使用echo %?来打印应用程序的退出代码。

结论

1) main函数,从函数调用链来看,仅仅是又一个非常普通的C函数,但同时也不普通,它唯一的代表了应用程序的入口点。

2) main函数的返回值将被操作系统用作进程的退出代码,利用这个特性,我们可以使用返回值来标识某项任务是否成功执行,例如,如果任务成功执行,则返回0,否则,返回与具体错误相关的整数,我的习惯是:使用大于0的整数来标识各种可能发生的错误,因为,一旦涉及到负数(有符号),水就比较深了。

继续阅读