exit() 和 _exit() 的区别
exit()和_exit()的效果都是让程序退出执行,而_exit()用来“尽快”退出。
atexit()
先说一下atexit()函数。我们可以用atexit()注册一个或多个函数退出清理函数(或者on_exit()但这个函数不建议用),这些清理函数按照注册时的反顺序,在exit()或main函数return时被调用。
#include <stdlib.h>
int atexit(void (*function)(void)); //return 0 on success.
注意,fork子进程时,这些函数会被继承到子进程。而exec系列执行成功后,所有函数会被清空。
exit()
我们知道父进程要wait子进程的退出状态,在子进程退出到父进程调用wait()期间,子进程就处于僵尸状态。因此,exit()将进程正常退出,并将(status & 0377)返回到父进程的wait(),其中status可以是EXIT_SUCCESS或EXIT_FAILURE。
#include <stdlib.h>
void exit(int status);
exit()在返回到父进程前要做的事情包括:按出栈顺序(反序)依次调用atexit()/on_exit()注册的函数。然后将所有打开的stdio流进行flush并关闭,如果有通过tmpfile()创建的文件也会被删除。(这里要注意,如果你注册的某个清理函数中调用_exit()或把自己kill结果退出了,那么后面的清理函数以及刷stdio等就不会执行了。所以,清理函数里不要调用exit()或_exit()。)
子进程exit()后会发送一个SIGCHLD信号给父进程(如果父进程设置了SA_NOCLDWAIT,那这个信号是否发送是未定义的)。
如果子进程在exit()后,父进程已经在等待(wait()系列函数)子进程的状态,或者父进程设置了SA_NOCLDWAIT或者将SIGCHLD的处理置为SIG_IGN,子进程都会立即退出。而如果父进程既没有wait,又没有设置忽略子进程退出,子进程就会变成僵尸进程(除了一个字节的exit status,什么都没有,用来确保以后某个时刻父进程wait的时候仍能拿到status,来解除子进程的僵尸状态)。如果父进程到死都没有wait,那僵尸进程会被init收养然后用wait清理掉它。
_exit()
而_exit()是企图让程序“立即”退出,它不会调用上述atexit()/on_exit()注册的函数。它也是会关闭自己打开的所有文件描述符的,但是否flush stdio以及是否删除tmpfile创建的文件则是与具体实现相关的(即没有明确规定)。我本地实验的结果是exit()会flush所有打开文件(stdio和其他文件)的缓冲,而_exit()不会。
#include <unistd.h>
void _exit(int status);
当然,由于_exit()会关闭文件描述符,所以可能也会有些delay(例如还没写完就close),如果想达到“立即”的目的,在_exit()之前调用一下tcflush()可能会有帮助。
exit()和return
在main()函数里调用exit()或return都可以使程序退出,但二者有一些差别:
- return是返回到main的调用者(如_libc_start_main),main()函数栈出栈,相关的局部变量被释放,调用者函数再让程序退出;
- exit()则是直接进入exit()函数去执行并退出函数。
无论哪种方式,它们都会调用exit_group(status)系统调用来让整个进程退出,其中参数status就是要发给父进程的状态码,即 exit(status) 或 return status; 中的status。
通常我们在main()之后就没什么待办的事情了,也不会关心exit()和return的差别,但对于C++程序而言,main()中对象的析构函数是在return之后执行的,如果中途调了exit()就不会执行到析构函数。
我们看下面这个程序:
#include <iostream>
#include <stdlib.h>
#include <string.h>
using namespace std;
void exit_func(void)
{
cout << "oh yeah!" << endl;
}
class Date
{
public:
Date(int year1 = 1970, int month1 = 12, int day1 = 31)
{
cout << "Date constructor" << endl;
this->year = year1;
this->month = month1;
this->day = day1;
}
void printDate()
{
cout << year << ":" << month << ":" << day << endl;
}
int isLeapYear() { return 1; }
void setDate(int year, int month, int day) {}
~Date() {cout << "Date destructor!" << endl;}
private:
int year;
int month;
int day;
};
class A
{
private:
int dataA;
public:
A() {cout << "A's constructor" << endl;}
~A() {cout << "A's destructor!" << endl;}
};
class B
{
private:
int dataB;
A dataClassA;
public:
B() {cout << "B's constructor" << endl;}
~B() {cout << "B's destructor!" << endl;}
};
static Date d199;
int main(int argc, char *argv[])
{
cout << "main start" << endl;
Date d1(2014, 1);
d1.printDate();
static Date d2;
A a1;
B b1;
atexit(exit_func);
cout << "main done" << endl;
return 0;
}
这个程序中有几点需要关注:1.定义了一个全局的对象d199,我们知道全局对象的构造是在main()之前做的,相应的全局对象或局部静态对象的析构在main()之后。2.我注册了一个atexit函数exit_func(),上面讲过它会在main()之后被执行。3.在main()函数最后我调用了return 0。
运行程序:
[[email protected]]diskroot:$ c++ newmain.c
[[email protected]]diskroot:$ ./a.out
Date constructor
main start
Date constructor
2014:1:31
Date constructor
A's constructor
A's constructor
B's constructor
main done
B's destructor!
A's destructor!
A's destructor!
Date destructor!
oh yeah!
Date destructor!
Date destructor!
可以看到,代码执行顺序是:全局对象的构造 -> 进入main() -> main()中对象的构造 -> main()返回 -> main()中对象的析构 -> atexit注册的退出函数 -> 全局和局部静态对象的析构。
接下来我们再看一下把 return 改为 exit 的结果(其他代码不变):
[[email protected]]diskroot:$ c++ newmain.c
[[email protected]]diskroot:$ ./a.out
Date constructor
main start
Date constructor
2014:1:31
Date constructor
A's constructor
A's constructor
B's constructor
main done
oh yeah!
Date destructor!
Date destructor!
可以看到,main()中定义的对象的析构没有被调用。
一般情况下,析构函数就是释放对象的资源,而进程退出后,进程所有资源就都被释放了,所以实际上调用exit()退出程序也并不会出现资源泄漏。只是说如果你的析构函数会涉及到与其他进程通信或IO操作等影响到系统其他资源的情况下就要注意了。
另外由于使用return的话,main()函数返回,其函数栈被释放,这对于vfork()会导致父进程还没用完的栈被破坏。不过vfork()已经不被使用了,现在的fork()也不是当年发明vfork()时的那个低效的fork()了,所以这个问题我们不用关心。
在main()最后什么都不写也就相当于return 0, 当然不建议这样写,最好还是明确返回值退出。
上一篇: Python函数作用域和匿名函数
下一篇: 批量修改文件后缀名
推荐阅读
-
软件测试学习 之 Python os._exit()&sys.exit()、exit(0)&exit(1) 的用法和区别
-
shell中关于exit的用法(后续更新。。)
-
exit() 和 _exit() 的区别
-
linux c 之perror和exit使用
-
【python】os._exit() 和 sys.exit(), exit(0)和exit(1) 的用法和区别
-
exit()和_exit()两个函数的区别
-
Tensorflow中Process finished with exit code -1073741819 (0xC0000005)的一种解决方案
-
exit()和_exit()的区别
-
exit()与_exit()函数的区别
-
formdata和requestpayload的区别解析