【筆記】C++的150個建議,第三章

目錄
第一部分 語法篇

  1. 第一章 從C繼承而來
  2. 第二章 從C到C++
  3. 第三章 內(nèi)存管理
  4. 第四章 類

第三章 內(nèi)存管理

在VC中,??臻g未初始化的字符默認是-52,補碼是0xCC。0xCCCC在GBK編碼中就是"燙"。
堆空間未初始化的字符默認是-51,兩個-51在GBK編碼中就是"屯"。

建議27:區(qū)分內(nèi)存分配的方式

程序由4部分組成:

  1. 代碼區(qū),存放程序的執(zhí)行代碼。無法控制。
  2. 數(shù)據(jù)區(qū),存放全局數(shù)據(jù)、常量、靜態(tài)變量等。自由存儲區(qū)、全局/靜態(tài)存儲區(qū)、常量存儲區(qū)。
  3. 堆區(qū),存放動態(tài)內(nèi)存
  4. 棧區(qū),存放程序中用到的局部數(shù)據(jù)。
    內(nèi)存的5個區(qū):
  5. 棧區(qū),存儲函數(shù)內(nèi)部變量,函數(shù)執(zhí)行完自動釋放。棧內(nèi)存分配運算內(nèi)置于處理器的指令集,效率很高,缺點是分配的內(nèi)存容量有限。
  6. 堆區(qū),由new運算符分配的內(nèi)存塊,由delete釋放。如果沒釋放,程序結束后操作系統(tǒng)自動回收。
  7. 自由存儲區(qū),由malloc等分配的內(nèi)存塊,用free釋放。
  8. 全局/靜態(tài)存儲區(qū),C的全局常量分為初始化和未初始化,C++里沒區(qū)分。
  9. 常量存儲區(qū),存放常量,不允許修改。

堆與棧的區(qū)別:

  1. 管理方式:棧由編譯器自動管理;對由開發(fā)人員自己管理。
  2. 空間大小:堆內(nèi)存可以占據(jù)整個內(nèi)存空間;棧內(nèi)存一般只有一定大小的空間。
  3. 碎片問題:頻繁地new/delete會造成內(nèi)存空間不連續(xù);棧的數(shù)據(jù)是連續(xù)的。
  4. 生長方向:堆是向上增長;棧是向下增長。
  5. 分配方式:堆都是動態(tài)分配的。棧可能是靜態(tài)或者動態(tài)分配的。棧的靜態(tài)分配由編譯器完成,動態(tài)分配由alloca函數(shù)完成,由編譯器釋放。
  6. 分配效率:堆內(nèi)存的分配效率很低,還可能引發(fā)用戶態(tài)和和心態(tài)的切換。棧內(nèi)存的分配效率很高。
    因此要在合適的地方采用合適的內(nèi)存分配方式。

建議28:new/delete與new[]/delete[]必須配對使用

內(nèi)置類型沒有構造、析構函數(shù),所以使用deletedelete[]一樣。

建議29:區(qū)分new的三種形態(tài)

  1. new operator,最常見的new運算符。
    語言內(nèi)建的,不能重載,也不能改變其行為。做如下三件事:
    (1)分配內(nèi)存(2)調(diào)用構造函數(shù)(3)返回對象的指針
  2. operator new,申請原始內(nèi)存。也就是new operator的第一步。與C庫中的malloc函數(shù)很像。
  3. placement new,選擇調(diào)用哪個構造函數(shù)。也就是new operator的第二步。

三種形態(tài)的用法:

  1. 如果只是想在堆上建立對象,使用new operator。
  2. 如果只是想分配內(nèi)存,使用operator new。
  3. 如果想在已經(jīng)獲得的內(nèi)存里建立一個對象,使用placement new。
    operator newplacement new的示例:
class A{
public:
    A();
    A(int a);
    ~A();
    void* operator new(size_t size);
    void* operator new[](size_t size);
    void operator delete(void*ptr, size_t sz);
    void operator delete[](void*ptr, size_t sz);

public:
    int a;
};

A::A():a(0)
{
    cout << "construct A" << endl;
}

A::A(int a):a(a)
{
    cout << "construct A" << endl;
}
A::~A() {
    cout << "destruct A" << endl;
}

void * A::operator new(size_t sz)
{
    cout << "custom operator new for size " << sz << endl;
    return ::operator new(sz);
}
void * A::operator new[](size_t sz)
{
    cout << "custom operator new for size " << sz << endl;
    return ::operator new(sz);
}

void A::operator delete(void* ptr, size_t sz)
{
    std::cout << "custom operator delete for size " << sz << endl;
    ::operator delete(ptr);
}
void A::operator delete[](void* ptr, size_t sz)
{
    std::cout << "custom operator delete for size " << sz << endl;
    ::operator delete(ptr);
}


int main(int argv, char* args[]) {
    cout << "1. operate new" << endl;
    A *ptrA = new A(123);     // operator new for size 4
    cout << "(*ptrA).a: " << (*ptrA).a << endl;
    cout << "sizeof ptrA: " << sizeof(ptrA) << endl;    // sizeof ptrA: 8
    delete ptrA;            // custom operator delete for size 4
    cout << endl;

    size_t len = 3;
    A *ptrB = new A[len];   // custom operator new for size 20
    cout << "sizeof ptrB: " << sizeof(ptrB) << endl;    // sizeof ptrB: 8
    cout << "sizeof A: " << sizeof(A) << endl;          // sizeof A: 4
    for (size_t ik = 0; ik < len; ++ik) {
        cout << "(*(ptrB+" << ik << ")).a: " << (*(ptrB + ik)).a << endl;
    }
    delete[] ptrB;          // custom operator delete for size 20, (20=8+4*3)

    cout << "\n2. placement new" << endl;
    void* s = operator new (sizeof(A));     // 分配內(nèi)存
    A* ptrC = (A*) s;
    ::new(ptrC) A(2019);  // 調(diào)用一個參數(shù)的構造函數(shù),ptrC->A::A(2019);
    cout << "(*ptrC).a: " << (*ptrC).a << endl;
    ::new(ptrC) A();         // 調(diào)用沒有參數(shù)的構造函數(shù),ptrC->A::A();
    cout << "(*ptrC).a: " << (*ptrC).a << endl;
    delete ptrC;
    return 0;
}

/* Output:
1. operate new
custom operator new for size 4
construct A
(*ptrA).a: 123
sizeof ptrA: 8
destruct A
custom operator delete for size 4

custom operator new for size 20
construct A
construct A
construct A
sizeof ptrB: 8
sizeof A: 4
(*(ptrB+0)).a: 0
(*(ptrB+1)).a: 0
(*(ptrB+2)).a: 0
destruct A
destruct A
destruct A
custom operator delete for size 20

2. placement new
construct A
(*ptrC).a: 2019
construct A
(*ptrC).a: 0
destruct A
custom operator delete for size 4
*/

建議30:new內(nèi)存失敗后的正確處理方式

malloc函數(shù)在申請內(nèi)存失敗后會返回NULL。
new在申請內(nèi)存失敗后會拋一個異常,不會執(zhí)行if(ptr == NULL){}

// new失敗拋異常
char *pStr = new string[SIZE];
if(pStr == NULL){
    // 如果new失敗,不會執(zhí)行
    return false;
}
// new失敗不拋異常
char *pStr = new(std::nothrow) string[SIZE];
if(pStr == NULL){
    // 如果new失敗,不會執(zhí)行
    return false;
}

如果想檢查new是否成功,則應該捕捉異常:

try{
    char *pStr = new string[SIZE];
}catch(const bad_alloc& e){
    return -1;
}

一般來說,new失敗可能是內(nèi)存不足引起的,捕獲這個異常沒有意義,可以直接core dump。

建議31:new內(nèi)存失敗后會調(diào)用new_handler函數(shù)

建議32:檢測內(nèi)存泄露工具

原理:截獲對內(nèi)存分配和內(nèi)存釋放的函數(shù)的調(diào)用。每當分配一塊內(nèi)存時,將其加入一個全局內(nèi)存鏈中,每當釋放一塊內(nèi)存時,將其刪除。程序結束后,內(nèi)存鏈中剩余的指針就是未被釋放的。
(1)Windows平臺:MS C-Runtime Libraay、BoundsChecker、Insure++
(2)Linux平臺:Rational Purify、Valgrind

建議33:operator new/operator delete的重載

  • 重載的operator new必須是類成員函數(shù)或全局函數(shù),不可以是某一個命名空間之內(nèi)的函數(shù)或全局靜態(tài)函數(shù)。
  • 因為operator new是在類的具體對象被構建出來之前調(diào)用的,在調(diào)用operator new的時候this指針尚未誕生,因此重載的operator new必須是static的。同理,operator delete也是static的。
  • operator newoperator delete成對重載
  • delete會調(diào)用free函數(shù)

建議34:智能指針

RAII(Resource Acquisition In Initialization)
智能指針實際是一個指針類,將指針通過構造函數(shù)傳遞給指針類的對象,當這個對象析構時,將指針delete。
不能使用臨時的share_ptr對象。當將share_ptr對象作為函數(shù)參數(shù)時,可能會由于函數(shù)參數(shù)求值順序,引起內(nèi)存泄露。

建議35:內(nèi)存池

當程序中需要頻繁地申請大小相同的內(nèi)存,用內(nèi)存池技術能提高效率。
內(nèi)存池技術通過批量申請內(nèi)存,降低內(nèi)存申請次數(shù),節(jié)省時間,且能減少內(nèi)存碎片的產(chǎn)生。

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴謹 對...
    cosWriter閱讀 11,680評論 1 32
  • 前言 不知道大家有沒有這樣一種感覺,程序員的數(shù)量井噴了??赡苁且驗榛ヂ?lián)網(wǎng)火了,也可能是各家培訓機構為我們拉來了大量...
    活這么大就沒飽過閱讀 2,846評論 6 25
  • 一、溫故而知新 1. 內(nèi)存不夠怎么辦 內(nèi)存簡單分配策略的問題地址空間不隔離內(nèi)存使用效率低程序運行的地址不確定 關于...
    SeanCST閱讀 8,139評論 0 27
  • 幾種語言的特性 匯編程序:將匯編語言源程序翻譯成目標程序編譯程序:將高級語言源程序翻譯成目標程序解釋程序:將高級語...
    囊螢映雪的螢閱讀 3,066評論 1 5
  • 內(nèi)存分類 在C++中,內(nèi)存分成5個區(qū),他們分別是堆、棧、自由存儲區(qū)、全局/靜態(tài)存儲區(qū)和常量存儲區(qū)。 棧:在執(zhí)行函數(shù)...
    碼哥說閱讀 544評論 0 3

友情鏈接更多精彩內(nèi)容