C++ 6 資源分派和釋放

來源:互聯網
上載者:User

6 資源分派和釋放

原則6.1 明確產品動態記憶體的申請與釋放原則

說明:之所以存在大量記憶體問題,主要原因是申請與釋放記憶體的規則混亂:

申請記憶體後,傳入子程式中,由子程式使用並釋放;

由子程式申請記憶體並返回父程式,層層調用後在某一個函數內釋放。
記憶體申請與釋放一般原則:

對象在退出其範圍時,就應該立即被釋放,而且要做到:誰申請,誰釋放。

函數內分配的記憶體, 函數退出之前要釋放,避免跨函數釋放;

類中資料成員的記憶體,在解構函式中確認並釋放;

全域變數、靜態變數的記憶體空間則在進程退出時,或相應的共用庫被卸載時,由作業系統回收;

如果程式分支很多或記憶體資源的分配與釋放不在同一個地方,要考慮使用RAII等資源跟蹤管理技

    術。

規則6.1 明確operator new的行為和檢查策略

說明:當operator new無法滿足某一記憶體配置需求時,預設會拋異常,也可以返回null 指標(通過編譯選

項設定)。團隊明確operator new的操作。在申請記憶體後,要立即檢查指標是否為NULL或進行異常處理。

樣本:捕獲異常來處理申請記憶體失敗情況

    char* pBuffer = NULL;
    try
    {
       pBuffer = new char[BUFFER_SIZE];
    }
    catch (...)
    {
       pBuffer = NULL;
       return EF_FAIL;
    }
或進行非空判斷:

    char* pBuffer = new char[BUFFER_SIZE];
    if (NULL == pBuffer)
    {
       return ER_FAIL;
    }

規則6.2 釋放記憶體後,要立即將指標設定為NULL,防止產生野指標

說明: free或delete釋放記憶體後,立即將指標設定為NULL,防止產生“野指標”。這種判斷最好能夠

封裝起來,見建議6.2。

樣本:

    char* pBuffer = new char[BUFFER_SIZE];
    //…
    delete [] pBuffer;
    pBuffer = NULL;

規則6.3 單個對象釋放使用delete,數組對象釋放使用delete []

說明:單個對象刪除使用delete, 數組對象刪除使用delete [],原因:

調用new所包含的動作:從系統中申請一塊記憶體,若是對象調用相應的建構函式。

調用new[n]所包含的動作:申請可容納n個對象外加一點記憶體來儲存數組的元素的數量;調用n次構造

函數初始化這塊記憶體中的n個對象。

調用delete所包含的動作:若是對象調用相應的解構函式;將記憶體歸還系統。

調用delete[]所包含的動作:從new[]將找出的n值;調用n次相應的解構函式;將記憶體歸還給系統。

樣本:

    std::string *string = new std::string;
    std::string *stringArray = new std::string[100];

    delete string;
    string = NULL;

    delete [] stringArray;
    stringArray = NULL;
如果使用delete stringArray;會導致stringArray指向的100個string對象中的99個未被銷毀,因為它們

的解構函式根本沒有被調用,並且結果是不可預測的,編譯器不同,結果也不同。

規則6.4 釋放結構(類)指標時,首先釋放其成員指標的記憶體空間

樣本:下面是一段有記憶體流失的產品代碼:

    struct STORE_BUF_S
    {
       ULONG   ulLen;
       UCHAR   *pcData;
    }STORE_BUF_T;

    void func()
    {
       STORE_BUF_T *pstStorageBuff = NULL;
       //申請結構記憶體….
       //程式處理…
       free(pstStorageBuff);
       return;
    }
先刪除了pstStorageBuff,pstStorageBuff->pcData永遠不可能被刪除了。刪除結構指標時,必須從底層

向上層順序刪除。即:

    free (pstStorageBuff->pcData);
    free(pstStorageBuff);

規則6.5 釋放指標數組時,首先釋放數組每個元素指標的記憶體

說明:在釋放指標數組時,確保數組中的每個元素指標是否已經被釋放了,這樣才不會導致記憶體流失。

樣本:

    struct dirent **namelist;
    int n = scandir(path.c_str(), &namelist, 0, alphasort);// 【1】

    int i = 0;
    for(i ; i < n; ++i)
    {
       string name = namelist[i]->d_name;
       free(namelist[i]); // 【2】
       if(name != ".." && name != ".")
        {
           //.........
           ++fileNum;
           if(MAX_SCAN_FILE_NUM == fileNum )//MAX_SCAN_FILE_NUM=1000
           {
              break;
           }
        }
    }
    free(namelist); // 【3】
    return ;
從上面的代碼可以看是指標數組namelist由系統函數進行分配記憶體(如【1】所示),記憶體釋放時時分別
由【2】釋放數組單個元素和 【3】釋放數組本身記憶體一起完成的。

但是中間有個條件,每次只取1000個檔案,如果目錄下的檔案大於1000就跳出,後面的就不會再處理

(【2】沒有執行到)。當本地目錄下檔案數比較小,小於等於1000時沒有記憶體流失;而當本地目錄下的

檔案大於1000時,就會導致記憶體流失。所以釋放指標數組時,請注意首先釋放其每個元素的記憶體空間。

正確的做法是在【3】之前加上:

    for(int j = i ; j < n; ++j)
    {
        free(namelist[i]);
    }

規則6.6 不要返回局部對象指標

說明:局部對象在定義點構造,在同一範圍結束時立即被銷毀。

樣本:

    char* GetParameter ()
    {
        CDBConnect DBConnect;
        //………………..
        return DBConnect.GetString("ParamValue");
    }
由於對象DBConnect已經析構,對應的指標已經被釋放,從而後續訪問非法記憶體,導致系統coredump。

規則6.7 不要強制關閉線程

說明:線程被強制關閉,導致線程內部資源泄漏。用事件或訊號量通知線程,確保線程調用自身的退

出函數。線程死結需要強制關閉的情況除外。

樣本:強制關閉線程,導致線程資源泄漏。

    CShakeHand::CShakeHand()
    {
       m_hdShakeThreadrecv = CreateThread(NULL, 0,
           (LPTHREAD_START_ROUTINE)ThreadProc_ShakeHands,
           this, NULL, &m_ulShakeThreadID);
    }
    CShakeHand::~CShakeHand()
    {
        TerminateThread(m_hdShakeThreadrecv, 0); //強制關閉
        CloseHandle(m_hdShakeThreadrecv);
    }

建議6.1 使用new, delete的封裝方式來分配與釋放記憶體

說明:推薦使用如下宏,可以在一定程度上避免使用null 指標,野指標的問題。

    #define HW_NEW(var, classname) \
        do { \
        try \
    { \
       var = new classname; \
    } \
        catch (...) \
    { \
       var = NULL; \
    } \
       break; \
        } while(0)
//(1)該宏會將var置為NULL, 所以調用該宏之後, 不再需要置var為NULL
    //(2) HW_DELETE宏與NEW對應, 用來釋放由HW_NEW分配的對象
    //   注意: 如果以數組方式指派至(見對HW_NEW的描述), 則必須使用宏HW_DELETE_A
    //   來釋放, 否則可能導致問題,參見:規則6.3
    #define HW_DELETE(var) \
       do \
    { \
       if (var != NULL) \
    { \
       delete var; \
       var = NULL; \
    } \
       break; \
    } while(NULL == var)

    //(1)這個宏用來刪除一個由HW_NEW分配的數組, 刪除之後也會將var置為NULL
    #define HW_DELETE_A(var) \
       do \
    { \
       if (var != NULL) \
    { \
       delete []var; \
       var = NULL; \
    } \
       break; \
    } while(NULL == var)
直接使用HW_DELETE,HW_DELETE_A宏來釋放指標記憶體空間,就不會出現遺忘將指標置為NULL

了。

建議6.2 避免在不同的模組中分配和釋放記憶體

說明:在一個模組中分配記憶體,卻在另一個模組中釋放它,會使這兩個模組之間產生遠距離的依賴,

使程式變得脆弱。

模組在C++是一個不清晰的概念,小到一個類,大到一個庫。如果在不同的類之間分配、釋放記憶體,需

要考慮兩個類的初始化、銷毀順序;如果在不同的庫之間分配、釋放記憶體,需要考慮兩個庫的載入或

卸載順序。這種遠距離的依賴,容易導致遺漏和重複操作,引發嚴重問題。

有時,在通訊機制下,兩個實體(如線程)之間交換資料或訊息,考慮到程式執行效率,不會採用拷貝

的方式交換資料,而是通過指標交換資料,仍然會在不同位置分配、釋放記憶體。這種情況下,只有數

據交換成功以後,才會由對方負責釋放,否則應遵循“誰申請、誰釋放”的原則。為了降低處理複雜

性,可以適當地採用RAII或智能指標。

建議6.3 使用 RAII 特性來協助追蹤動態分配

說明:RAII是“資源擷取就是初始化”的縮語(Resource Acquisition Is Initialization),是一種

利用對象生命週期來控製程序資源(如記憶體、檔案控制代碼、網路連接、互斥量等等)的簡單技術。

RAII 的一般做法是這樣的:在物件建構時擷取資源,接著控制對資源的訪問使之在對象的生命週期內

始終保持有效,最後在對象析構的時候釋放資源。這種做法有兩大好處:

我們不需要顯式地釋放資源。

對象所需的資源在其生命期內始終保持有效。這樣,就不必檢查資源有效性的問題,可以簡化邏輯、

提高效率。
C++類庫的智能指標就是其中之一:

auto_ptr是標準C++庫提供的一種模板類,使用指標進行初始化,其訪問方式也和指標一樣。在auto_ptr

退出範圍時,所指對象能被隱式的自動刪除。這樣可以象使用普通指標一樣使用auto_ptr,而不用

考慮釋放問題。注意:auto_ptr的複製會造成它本身的修改,原有的auto_ptr將不再指向任何對象,

而由新的auto_ptr接管對象記憶體,並負責自動刪除。因此auto_ptr複製後不能再使用,且不能複製const

auto_ptr。

boost庫中提供了一種新型的智能指標shared_ptr,它解決了多個指標間共用對象所有權的問題,同時

也滿足容器對元素的要求,因而可以安全地放入容器中。shared_ptr解決了auto_ptr移動語意的破壞

性。

關於auto_ptr與shared_ptr使用請參考C++標準庫的相關書籍。

樣本:使用RAII不需要顯式地釋放互斥資源。

    class My_scope_lock
    {
    public:
       My_scope_lock(LockType& _lock):m_lock(_lock)
        {
           m_lock.occupy();
        }
       ~My_scope_lock()
        {
           m_lock.relase();
        }
    protected:
       LockType    m_lock;
    }
    bool class Data::Update()
    {
       My_scope_lock l_lock(m_mutex_lock);
       if()
        {
           return false;
        }
       else
        {
           //execute
        }
       return true;
    }

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.