atexit() 和 exit() 函数

来源:互联网 发布:稀客 杨千嬅 知乎 编辑:程序博客网 时间:2024/06/05 21:02


我们通常认为C语言的起始函数是main函数,实质上一个程序的启动函数并不一定是main函数,这个可以采用链接器来设置,但是gcc中默认main就是C语言的入口函数,在main函数启动之前,内核会调用一个特殊的启动例程,这个启动例程从内核中取得命令行参数值和环境变量值,为调用main函数做好准备,因此对应程序而言main函数并不是起始,但是对应C语言而言,main函数就是入口地址,其他的链接器帮助我们完成,实际上mian函数的执行是使用了exec函数,这是一个函数族,这也是内核执行一个程序的唯一方法,这在进程控制部分将进行分析[1]

 

而在main()函数执行完之后是不是就完事儿了呢(有面试题会问在main函数退出之后,是否还可以执行程序)?下面介绍两个函数:

 

一、exit()函数
所在头文件:stdlib.h

:关闭所有文件,终止正在执行的进程

exit(1)表示异常退出.这个1是返回给操作系统的

exit(x)x不为0)都表示异常退出

exit(0)表示正常退出

exit()的参数会被传递给一些操作系统,包括UNIX,Linux,MS DOS,以供其他程序使用

函数声明:void exit(intstatus);
: status //程序退出的返回值

 

exit()函数用于在程序运行的过程中随时结束程序,exit的参数state是返回给操作系统,返回0表示程序正常结束,非0表示程序非正常结束。main函数结束时也会隐式地调用exit函数。exit函数运行时首先会执行由atexit()函数登记的函数,然后会做一些自身的清理工作,同时刷新所有输出流、关闭所有打开的流并且关闭通过标准I/O函数tmpfile()创建的临时文件[1]。 

 

另外:关于abort、exit,_exit, _Exit进程终止函数[3]:
abort:产生SIGABRT信号。非正常退出,即在程序碰到灾难性错误时强制退出。由于是非正常退出,因此不会做其他任何操作。

_exit:是一个系统调用,直接使进程停止运行,清除其使用的内存空间,并销毁其在内核中的各种数据结构。此函数不会调用atexit注册的函数,也不会运行信号处理程序。对标准IO流的缓冲区是否进行刷新取决于该函数在系统中的实现。一般UNIX下不会刷新。exit函数会调用此函数。

exit:是一个C库标准函数,在以上基础上作了一些包装,在执行退出之前加了若干道工序。执行此函数会调用由atexit注册的函数,然后执行关闭所有标准IO流,刷新缓冲区等操作。对于常用的有返回值的return调用,相当于调用了exit。如return(0)==exit(0)。

_Exit:是一个C库标准函数。其动作类似_exit。

 

exit()_exit()以及_Exit()函数的本质区别是是否立即进入内核,_exit()以及_Exit()函数都是在调用后立即进入内核,而不会执行一些清理处理,但是exit()则会执行一些清理处理,这也是为什么会存在atexit()函数的原因,因为exit()函数需要执行清理处理,需要执行一系列的操作,这些终止处理函数实际上就是完成各种所谓的清除操作的实际执行体。atexit函数的定义也给了程序员一种运用exit执行一些清除操作的方法,比如有一些程序需要额外的操作,具体的清除操作可以采用这种方法对特殊操作进行清除等。

 

内核使程序执行的唯一方法是调用一个exec函数,进程自愿终止哦的唯一方法是显式或者隐式调用(通过exit函数)_exit()或者_Exit()函数。因此exit函数中实质是对_exit()或者_Exit()函数的封装。exit会先执行自定义的终止处理函数,然后执行I/O库函数清理函数fclose(),这也是为什么可以在终止处理函数中可以继续运用printf之类函数的原因,因为I/O库函数的流对象还没有被清除,当然可以继续运用。执行完了所有的fclose()以后,可以执行真正意义上的终止函数_exit()或者_Exit()函数。

 

 

二、atexit()函数

头文件:#include<stdlib.h>

:注册终止函数(main执行结束后调用的函数)

函数声明: voidatexit(void  (*func)(void));

 

很多时候我们需要在程序退出的时候做一些诸如释放资源的操作,但程序退出的方式有很多种,比如main()函数运行结束、在程序的某个地方用exit()结束程序、用户通过Ctrl+CCtrl+break操作来终止程序等等,因此需要有一种与程序退出方式无关的方法来进行程序退出时的必要处理。方法就是用atexit()函数来注册程序正常终止时要被调用的函数。atexit()函数的参数是一个函数指针,函数指针指向一个没有参数也没有返回值的函数。

 

在一个程序中最多可以用atexit()注册32个处理函数,这些处理函数的调用顺序与其注册的顺序相反,也即最先注册的最后调用,最后注册的最先调用。

下面是一段代码示例:

<span style="font-size:14px;">#include <stdlib.h> #include <iostream.h>void terminateTest(){    cout<<"程序正在结束..."<<endl;}int main(void){    // 注册退出处理函数    atexit(terminateTest);    cout<<"the end of main()"<<endl;    return 0;}</span>

程序的运行结果为:


the end of main()
程序正在结束...

 

 

这些函数都是在main结束以后才被调用的。atexit只是注册他们,使得他们在main结束以后被调用,看名字就可以看出来。atexit这个玩意超有用,可以按照你予设的顺序摧毁全局变量(类),例如有个log类,你在其它的全局类里也有可能调用到Log类写日志。所以log类必须最后被析构。假如没有规定析构顺序,那么程序在退出时将有可能首先析构log类,那么其它的全局类在此时将无法正确写日志。把数据写回文件,删除临时文件,这才是真正有用的。

 

atexit与析构函数的调用顺序

atexit(f)调用之前被静态分配的对象的析构函数将在f()的调用之后被调用。在一个atexit(f)调用之后建立的这种对象的析构函数将在f的调用之前被调用。引自《The C++Programming Language》(BjarneStroustrup)。

 

参考:

[1] http://blog.chinaunix.net/uid-20937170-id-3447901.html

[2] http://blog.sina.com.cn/s/blog_5cec5bad0100b0x2.html

[3] http://blog.163.com/muren20062094@yeah/blog/static/161844416201152054152877/

[4] http://baike.baidu.com/view/1542946.htm?fr=aladdin

0 0