溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

C++動態(tài)內(nèi)存管理實例分析

發(fā)布時間:2022-07-21 09:27:09 來源:億速云 閱讀:140 作者:iii 欄目:開發(fā)技術

本篇內(nèi)容介紹了“C++動態(tài)內(nèi)存管理實例分析”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!

    C/C++ 內(nèi)存分布

    我記得,在初識C語言那里就和大家分享了程序虛擬地址空間的概念,無論是C語言的nalloc函數(shù),還是我們現(xiàn)在要分享的new,都是在堆區(qū)開辟空間,這一點是我們要首先記得的。

    C++動態(tài)內(nèi)存管理實例分析

    C語言內(nèi)存管理方式

    C語言是通過函數(shù)來經(jīng)行動態(tài)的內(nèi)存開辟的,標準庫里面提供三個函數(shù),這里我就不加贅述了,大家應該都是知道的。我么看看用法就可以了。

    #include <stdio.h>
    #include <assert.h>
    
    int main()
    {
    // malloc 開辟空間 不初始化
    int* p1 = (int*)malloc(sizeof(int)* 4);
    assert(p1);
    
    //calloc 開辟空間 初始化 為 0
    int* p2 = (int*)calloc(4, sizeof(int));
    assert(p2);
    // 追加 空間
    p1 = (int*)relloc(p1, sizeof(int)* 8);
    
    free(p1);
    free(p2);
    return 0;
    }

    C++內(nèi)存管理方式

    C++是支持C語言的,也是說C++是可以使用這些函數(shù)的,但是除了這些函數(shù)外,C++有增加了new和delete這個兩個關鍵字,分別對標的malloc/calloc和free,而且C++的方式比C的好用.

    C++為何增加了new 和 delete

    我們都知道,C語言的結構體里面不支持函數(shù),所以大佬們提出了類的概念,出現(xiàn)了class,又害怕自己有時后可能忘記初始化和清除掉內(nèi)存,就出現(xiàn)了構造函數(shù)和析構函數(shù),讓編譯器自動調(diào)用,可以說,所有的事物的出現(xiàn)都是為了我們更好的使用語言,new和delete也似乎如此,C語言的動態(tài)內(nèi)存開辟是有一定的麻煩的,而且對于自動類型很不友好,后面我們就會比較他們的優(yōu)劣.

    我們還發(fā)現(xiàn)一個很直接問題,每一次開辟空間我們都要強制類型轉換,而且還需要判斷內(nèi)存是不是究竟開出來了,這也太麻煩了,new卻不會出現(xiàn)這種事,如果沒有開辟出,編譯器會拋異常,我們就不需要再自己手動檢測了.

    new 一個對象

    這樣,我先和大家演示內(nèi)置類型,自定義類型那里我準備專門和malloc比較一下.

    #include <iostream>
    using namespace std;
    
    int main()
    {
    int* p1 = new int;
    *p1 = 10;
    cout << *p1 << endl;
    return 0;
    }

    C++動態(tài)內(nèi)存管理實例分析

    我們也知道,再C++中,內(nèi)置類行也被作為類了,我們可以再new的時候對它進行初始化.

    int main()
    {
    int* p = new int(0);
    cout << *p << endl;
    
    return 0;
    }

    C++動態(tài)內(nèi)存管理實例分析

    new 一個數(shù)組

    new一個數(shù)組更是簡單,我們直接寫出來就可以了.

    int main()
    {
    int* p = new int[10]; // new 一個 10 個int 類行的空間
    return 0;
    }

    C++動態(tài)內(nèi)存管理實例分析

    我們也可以在new空間的時候進行實例化,不過要顯示實例化

    int main()
    {
    int* p = new int[10]{1,2,3};
    return 0;
    }

    C++動態(tài)內(nèi)存管理實例分析

    delete

    大家可能發(fā)現(xiàn),我上面都沒有釋放空間,這會造成內(nèi)存泄漏,這里我們用另一個關鍵字delete,這里就比較簡單了.

    大家可能疑惑delete[],這里我們記住就可以了,如果你要清除數(shù)組的空間,最好使用這種方式,或許對于內(nèi)置類行,使用delete也可以,但是對于自定義類行可能會報錯,這里我也放在后面談.

    int main()
    {
    int* p1 = new int;
    int* p2 = new int[10]{1,2,3};
    delete p1;
    delete[] p2;
    return 0;
    }

    C++動態(tài)內(nèi)存管理實例分析

    malloc & new

    我們需要對比一下malloc和new它們之間的區(qū)別,這樣就可以知道C++為何這么喜歡new了.

    內(nèi)置類型

    我們先下一個結論,它們兩個對于內(nèi)置類行除了報錯之外是沒有任何區(qū)別的,都不會經(jīng)行初始化,這里我們現(xiàn)不談報錯的信息,異常和沒有和大家分享.

    int main()
    {
    int* p1 = new int[10];
    
    int* p2 = (int*)malloc(sizeof(int)* 10);
    assert(p2);
    
    delete[] p1;
    free(p2);
    return 0;
    }

    C++動態(tài)內(nèi)存管理實例分析

    自定義類型

    對于自定義類型,它們的差別可大了去了.

    我們先來準備一個類:

    class A
    {
    public:
    A(int a = 0,int b=0)
    :_a(a)
    , _b(b)
    {
    cout << "構造函數(shù)" << endl;
    }
    ~A()
    {
    cout << "析構函數(shù)" << endl;
    }
    private:
    int _a;
    int _b;
    };

    malloc是直接開辟空間,對于里面的構造函數(shù)是不會調(diào)用的,free的時候也不會調(diào)用析構函數(shù)

    int main()
    {
    A* aa = (A*)malloc(sizeof(A));
    free(aa);
    return 0;
    }

    C++動態(tài)內(nèi)存管理實例分析

    new 和 delete會分別調(diào)用構造函數(shù)和析構函數(shù),完成初始化

    int main()
    {
    A* aa = new A;
    delete aa;
    return 0;
    }

    C++動態(tài)內(nèi)存管理實例分析

    operator new與operator delete函數(shù)

    這里一看像是new和delete的重載,記住,這不是,就是名字有點奇怪罷了.這是C++里面的全局函數(shù),它的使用方法和malloc一樣,而且作用也是有一樣的,不會調(diào)用構造函數(shù)和析構函數(shù).

    new和delete是用戶進行動態(tài)內(nèi)存申請和釋放的操作符,operator new 和operator delete是系統(tǒng)提供的全局函數(shù),new在底層調(diào)用operator new全局函數(shù)來申請空間,delete在底層通過operator delete全局函數(shù)來釋放空間

    int main()
    {
    A* aa = (A*)operator new(sizeof(A));
    operator delete (aa);
    return 0;
    }

    C++動態(tài)內(nèi)存管理實例分析

    原理

    通過源碼我們就會發(fā)現(xiàn),實際上operator new與operator delete函數(shù) 本質(zhì)上是malloc和free的封裝,就是報錯的信息有點不同,封裝的報錯的信息是異常.

    operator new 的原理是 malloc

    void *__CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc) { // try to allocate size bytes void *p; while ((p = malloc(size)) == 0) if (_callnewh(size) == 0) { // report no memory // 如果申請內(nèi)存失敗了,這里會拋出bad_alloc 類型異常 static const std::bad_alloc nomem; _RAISE(nomem); } return (p); }

    operator delete 原理

    void operator delete(void *pUserData) { _CrtMemBlockHeader * pHead; RTCCALLBACK(_RTC_Free_hook, (pUserData, 0)); if (pUserData == NULL) return; _mlock(_HEAP_LOCK); /* block other threads */ __TRY /* get a pointer to memory block header */ pHead = pHdr(pUserData); /* verify block type */ _ASSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse)); _free_dbg(pUserData, pHead->nBlockUse); // 注意 C語言的free 就是這個函數(shù) __FINALLY _munlock(_HEAP_LOCK); /* release other threads */ __END_TRY_FINALLY return; }

    為何出現(xiàn)這兩個函數(shù)

    這兩個函數(shù)不是給我們調(diào)用的,是為了new的底層調(diào)用的,我們new一個對象,就相當于call operator new 和 call 對象的構造函數(shù),這才是它們出現(xiàn)的原因.

    大家可以看看反匯編.

    C++動態(tài)內(nèi)存管理實例分析

    delete & delete[]

    這個我們可以這么理解,對于內(nèi)置類型,它們就沒必要討論的,作用差不多.但是對于自定義類型就有很大的問題.

    • 在釋放的對象空間上執(zhí)行N次析構函數(shù),完成N個對象中資源的清理

    • 調(diào)用operator delete[]釋放空間,實際在operator delete[]中調(diào)用operator delete來釋放空間

    大家先看看結果:

    delete[] 析構相應的的次數(shù)

    int main() { A* aa = new A[3]; delete[] aa; return 0; }

    C++動態(tài)內(nèi)存管理實例分析

    delete 析構一次,還會報錯

    int main() { A* aa = new A[3]; delete aa; return 0; }

    C++動態(tài)內(nèi)存管理實例分析

    內(nèi)存池

    這里我想提一個概念,我們都知道m(xù)alloc和new都是在堆上開辟空間,如果我們要是多次的去開辟空間,效率是不是有點慢,想一想,我們一次開辟一次,開了個上千次,每次都要去申請,我們在想,能不能單獨的劃分出一塊區(qū)域,專門提供我們想要的對象來開辟空間,這就是內(nèi)存池最初的想法,大家可能會感到疑惑,內(nèi)存池和堆有什么不同嗎,簡單來說,內(nèi)存池離你近,可以提高效率。我們可以這么類比,堆就像每噸你在學校吃飯就和你老爸要錢,每頓都要,那么內(nèi)存池就像月初你直接和你爸要好這個月的生活費,一月要一次,肯定是后者的效率比較高的。

    那么我們該如何使用內(nèi)存池,標準庫里面也提供了一個,這里我們需要在類內(nèi)重寫operator new與operator delete函數(shù)函數(shù),大家先來了解一下用法就可以了,我們先不來細究,后面可能會有一個高并發(fā)內(nèi)存池的項目要和大家分享,不過這個時間就有點長了。

    struct ListNode
    {
    ListNode* _next;
    ListNode* _prev;
    int _data;
    
    // 申請空間的是后去內(nèi)存 池
    void* operator new(size_t n)
    {
    void* p = nullptr;
    p = allocator<ListNode>().allocate(1);
    cout << "memory pool allocate" << endl;
    return p;
    }
    void operator delete(void* p)
    {
    allocator<ListNode>().deallocate((ListNode*)p, 1);
    cout << "memory pool deallocate" << endl;
    }
    };
    class List
    {
    public:
    List()
    {
    _head = new ListNode;
    _head->_next = _head;
    _head->_prev = _head;
    }
    ~List()
    {
    ListNode* cur = _head->_next;
    while (cur != _head)
    {
    ListNode* next = cur->_next;
    delete cur;
    cur = next;
    }
    delete _head;
    _head = nullptr;
    }
    private:
    ListNode* _head;
    };
    
    int main()
    {
    List l1;
    return 0;
    }

    C++動態(tài)內(nèi)存管理實例分析

    定位 new

    我們已經(jīng)知道了,使用operator new開辟出的空間是不會初始化的,而且現(xiàn)在我們是無法通過對象來顯式調(diào)用構造函數(shù)的,這也就意味著我們要是向修改成員變量,一定會破壞封裝.但是C++這里也提供了一個定位new的技術可以幫助我們再次實例化,我們先來看看用法.

    class A
    {
    public:
    A(int a = 0)
    :_a(a)
    {
    }
    private:
    int _a;
    };
    
    int main()
    {
    A* a = (A*)operator new(sizeof(A));
    
    // 定位 new
    new(a)A (1);
    return 0;
    }

    C++動態(tài)內(nèi)存管理實例分析

    從這里我們就可以知道了,定位new有下面兩種用法

    • new(要初始化的指針) 指針解引用對應的類行 直接調(diào)用默認構造函數(shù)

    • new(要初始化的指針) 指針解引用對應的類行 (構造函數(shù)要傳的參數(shù)) 調(diào)用相應的構造函數(shù)

    “C++動態(tài)內(nèi)存管理實例分析”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關的知識可以關注億速云網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實用文章!

    向AI問一下細節(jié)

    免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權內(nèi)容。

    c++
    AI