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.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()了,所以这个问题我们不用关心。

在main()最后什么都不写也就相当于return 0, 当然不建议这样写,最好还是明确返回值退出。


原文:https://blog.csdn.net/jasonchen_gbd/article/details/80795655 
 

猜你喜欢

转载自blog.csdn.net/weixin_42625444/article/details/84644042
今日推荐