exit() 和 _exit()函数详解
exit()
就是让程序退出,传入的参数是程序退出时的状态码,0 表示正常退出,其他表示非正常退出。标准中有:EXIT_SUCCESS
和 EXIT_FAILURE
两个宏。
作为系统调用,_exit
和 exit
是基本一样的。在内核中,有如下的定义
1 | #define __NR__exit __NR_exit |
以下再说几点区别:
exit()
函数定义在stdlib.h
中,而_exit()
定义在unistd.h
中_exit()
函数的作用最为简单:直接使进程停止运行,清除其使用的内存空间,并销毁其在内核中的各种数据结构;exit()
函数则在这些基础上做了一些包装,在执行退出前加了若干道工序。- 两者最大的区别在于
exit()
函数在执行前要先检查文件的打开情况,把文件缓冲区中的内容写回文件,就是“清理 IO 缓冲”
1. exit() 在结束调用它的进程之前,要进行如下步骤
- 调用
atexit()
注册的函数,按 atexit 注册时相反的顺序调用所有由它注册的函数,这使得我们可以指定在程序终止时执行自己的清理动作。例如,保存程序状态信息于某个文件,解开对共享数据库的锁等 cleanup()
关闭所有打开的流,这将导致写所有被缓冲的输出到文件;删除用 tmpFile 函数建立的所有临时文件- 最后调用
_exit()
函数终止进程
2. _exit() 做如下事情
- 关闭属于该进程的任何打开的文件描述符
- 让该进程的所有子进程都由 init(1号进程)继承
- 向该进程的父进程发送 SIGCHLD 信号
3. 注意
- 对于有缓冲的 IO,比如
printf、fopen、fread、fwrite
等等,如果调用_exit()
函数直接将进程关闭,可能会使缓冲区中的数据丢失。如果想要保证数据的完整性,就一定要使用exit()
函数 - 相同点,不管进程是如何终止的,内核都会关闭进程打开的所有文件描述符,释放进程使用的内存。
在一个 fork 的子进程分支中,使用 _exit 函数而不是 exit 函数?
如果在 fork 创建的子进程分支中使用 exit 函数,会导致标准输入输出的缓冲区被清空两次,而临时文件被出乎意料的删除(临时文件由 tmpfile 函数创建在系统临时目录下,文件名由系统随机生成)。在 c++ 程序中清空会更糟,因为静态目标的析构函数可以被错误的执行。
还有一些特殊情况,比如守护进程,他们的父进程需要调用 _exit 而不是子进程。对于大多数情况,exit 在每一次进入 main 函数后只调用一次即可。