天天看点

exit() 和 _exit() 的区别

博主福利:100G+电子设计学习资源包!

​​http://mp.weixin.qq.com/mp/homepage?__biz=MzU3OTczMzk5Mg==&hid=7&sn=ad5d5d0f15df84f4a92ebf72f88d4ee8&scene=18#wechat_redirect​​ --------------------------------------------------------------------------------------------------------------------------

exit()和_exit()的效果都是让程序退出执行,而_exit()用来“尽快”退出。

atexit()

先说一下atexit()函数。我们可以用atexit()注册一个或多个函数退出清理函数(或者on_exit()但这个函数不建议用),这些清理函数按照注册时的反顺序,在exit()或main函数return时被调用。

#include <stdlib.h>

int atexit(void (*function)(void)); //return 0 on success.12

注意,fork子进程时,这些函数会被继承到子进程。而exec系列执行成功后,所有函数会被清空。

exit()

我们知道父进程要wait子进程的退出状态,在子进程退出到父进程调用wait()期间,子进程就处于僵尸状态。因此,exit()将进程正常退出,并将(status & 0377)返回到父进程的wait(),其中status可以是EXIT_SUCCESS或EXIT_FAILURE。 

#include <stdlib.h>

void exit(int status);12

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);12

当然,由于_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。 

运行程序:

[root@ubuntu]diskroot:$ c++ newmain.c 
 [root@ubuntu]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 的结果(其他代码不变):

[root@ubuntu]diskroot:$ c++ newmain.c 
 [root@ubuntu]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()了,所以这个问题我们不用关心。