1. 程式人生 > >exit() 和 _exit() 的區別

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。 
執行程式:

 

[[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, 當然不建議這樣寫,最好還是明確返回值退出。


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