內存分布:
首先我們需要了解的是C/C++中內存區域的劃分:
1. 棧又叫堆棧--非靜態局部變量/函數參數/返回值等等,棧是向下增長的:先調用的地址比后調用的地址大。
2. 內存映射段是高效的I/O映射方式,用于裝載一個共享的動態內存庫。用戶可使用系統接口
創建共享共享內存,做進程間通信。
3. 堆用于程序運行時動態內存分配,堆是可以上增長的:先調用的地址比后調用的地址小。
4.?數據段--存儲全局數據和靜態數據。
5. 代碼段--可執行的代碼/只讀常量。
當我們懂了內存分布,可以嘗試練習:
是不是前面還算簡單,下面就寫懵圈了!
解析:
? ? ? ? char2是定義在棧上的,它將在常量區的“abcd”拷貝的一份到數組中,,所以解引用(數組名是數組的首地址)還是是在棧上;
? ? ? ? pChar3是一個指向常量區的“abcd”的一個指針,定義在棧上,解引用后就是常量區的“abcd”所以解引用在常量區;
? ? ? ? ptr1定義在棧上,但是malloc開辟的空間在堆上,所以解引用是在堆上;
棧和堆的區別:?
棧和堆的區別:
????????棧:由編譯器自動分配并且出了作用域就釋放,一般存儲函數的參數局部變量等。
????????堆:由我們通過開辟的空間分配,需要我們手動釋放,若程不釋放則系統釋放。1、申請內存的方式:
棧:由編譯器自動分配,如變量的聲明的同時會開辟空間。堆:由程序員申請,需要制定需要的大小(動態分配)。
2、系統響應的不同:棧:只要系統剩余空間大于申請內存,系統就會提供,否則程序會崩潰流:棧溢出。
堆:遍歷空閑地址鏈表,找到符合要求的,就將該地址分配給程序,內存的首地址記錄分配的大小(方便delete)。
3、空間大小不同:
棧:連續的,編譯時就確定的大小;
堆:不連續,他的上限決定于系統中有效的虛擬內存。
4、執行效率的不同:
棧:由系統分配,速度快;
堆:程序員分配,速度慢,容易產生內存碎片,需要多少開辟多少。
C語言中動態內存管理方式:malloc/calloc/realloc/free
malloc/calloc/realloc的區別:
? ? ? ? 1.malloc和realloc分配好內存空間后不會對空間初始化,calloc會對空間全部初始化為0.
? ? ? ? 2.參數不同:malloc(需要開辟的大小)、calloc(需要開辟個數,每個的大小)、realloc(原來的指針,新的內存塊大小)。
? ? ? ? 3.它們空間分配失敗的返回值地址都為NULL;
? ? ? ? 4.它們都需要free釋放內存,否則會造成內存泄漏等問題。
C++內存管理方式
????????C語言內存管理方式在C++中可以繼續使用,但有些地方就無能為力,而且使用起來比較麻煩,因此C++又提出了自己的內存管理方式:通過new和delete操作符進行動態內存管理。
new/delete操作內置類型
注意:在申請自定義類型的空間時,new會調用構造函數,delete會調用析構函數,而malloc與free不會。
#include <iostream>
using namespace std;class Stack
{
public:Stack(int capacity = 4){cout << "構造函數" << endl;_capacity = capacity;_a = new int[_capacity];_top = 0;}~Stack(){cout << "析構函數" << endl;delete[] _a;_a = nullptr;_top = 0;_capacity = 0;}
private:int* _a;int _capacity;int _top;
};int main()
{Stack s1;//Stack* p1 = new Stack;//1.new先開辟一個Stack大小的空間;2.然后調用構造函數在開辟一個棧的空間//delete p1;//1.先釋放構造函數開辟的空間;2.再釋放new開辟的Stack的空間//Stack* p2 = (Stack*)operator new(sizeof(Stack));//沒有去調用構造函數初始化//operator delete(p2);//沒有去調用析構函數,導致內存泄漏:給棧開辟的空間沒有釋放//Stack* p3 = new Stack[10];//這里開辟的空間會多開4個,用于存放開辟的數量,便于delete使用delete[] p3;//delete p3;//空間不能局部釋放:直接去釋放Stack的空間,前面還有用于記錄數量的空間,所以程序崩潰return 0;
}
操作自定義類型:
?new/delete 和 malloc/free最大區別是 new/delete對于【自定義類型】除了開空間還會調用構造函數和析構函數!!!
class A
{
public:A(int a = 0): _a(a){cout << "A():" << this << endl;}//~A()//{// cout << "~A():" << this << endl;//}private:int _a;
};int main()
{A* p4 = new A[10];//delete p4;//如果沒有定義析構是不會出錯:因為編譯器的優化認為不需要調用析構,所以沒有多開辟空間來記錄開辟的個數free(p4);//同理return 0;
}
//通過上面兩個程序的比較:new/delete new[]/delete[] malloc/free 一定要配對使用,否則結果是未定義
注意:
????????申請和釋放單個元素的空間,使用new和delete操作符,申請和釋放連續的空間,使用
new[]和delete[],注意:new和delete匹配起來使用。
了解operator new與operator delete函數
????????new和delete是用戶進行動態內存申請和釋放的操作符,operator new 和operator delete是系統提供的全局函數,new在底層調用operator new全局函數來申請空間,delete在底層通過operator delete全局函數來釋放空間。
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
?? ??? ??? ?// 如果申請內存失敗了,這里會拋出bad_alloc 類型異常
?? ??? ??? ?static const std::bad_alloc nomem;
?? ??? ??? ?_RAISE(nomem);
?? ??? ?}
?? ?return (p);
}可以看到operator new?函數實際還是通過malloc來申請空間,當malloc申請空間成功時直接返回;申請空間失敗,嘗試執行空間不足應對措施,如果改應對措施用戶設置了,則繼續申請,否則拋異常。可以理解為:為了解決C語言中將malloc失敗的NULL設置為0,導致一些特殊場景的錯誤,所以將malloc進行封裝了,對malloc失敗進行異常拋出。
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);
?? ?__FINALLY
?? ??? ?_munlock(_HEAP_LOCK); /* release other threads */
?? ?__END_TRY_FINALLY
?? ??? ?return;
}operator delete函數本質還是通過free來釋放空間,
????????通過上述兩個全局函數的實現知道,operator new 實際也是通過malloc來申請空間,如果malloc申請空間成功就直接返回,否則執行用戶提供的空間不足應對措施,如果用戶提供該措施就繼續申請,否則就拋異常。operator delete 最終是通過free來釋放空間的。
new和delete的實現原理
內置類型:
????????如果申請的是內置類型的空間,new和malloc,delete和free基本類似,不同的地方是:
new/delete申請和釋放的是單個元素的空間,new[]和delete[]申請的是連續空間,而且new在申請空間失敗時會拋異常,malloc會返回NULL。
自定義類型:
new的原理:
????????1. 調用operator new函數申請空間
????????2. 在申請的空間上執行構造函數,完成對象的構造
delete的原理:
????????1. 在空間上執行析構函數,完成對象中資源的清理工作
????????2. 調用operator delete函數釋放對象的空間
new T[N]的原理:
????????1. 調用operator new[]函數,在operator new[]中實際調用operator new函數完成N個對象空間的申請:
????????2. 在申請的空間上執行N次構造函數
delete[]的原理:
????????1. 在釋放的對象空間上執行N次析構函數,完成N個對象中資源的清理
????????2. 調用operator delete[]釋放空間,實際在operator delete[]中調用operator delete來釋放空間
定位new表達式(placement-new)
定位new表達式是在已分配的原始內存空間中調用構造函數初始化一個對象。
使用格式:
????????new (place_address) type或者new (place_address) type(initializer-list)
????????place_address必須是一個指針,initializer-list是類型的初始化列表
使用場景:
????????定位new表達式在實際中一般是配合內存池使用。因為內存池分配出的內存沒有初始化,所以如果是自定義類型的對象,需要使用new的定義表達式進行顯示調構造函數進行初始化
class A
{
public:A(int a = 0): _a(a){cout << "A():" << this << endl;}//~A()//{// cout << "~A():" << this << endl;//}private:int _a;
};int main()
{A a1;//自動調用構造函數//構造函數可以顯示調用嗎?A* p1 = (A*)operator new(sizeof(A));//不能下面這樣顯示調用構造函數//p1->A(1);//但是可以用定位new顯示調用構造函數new(p1)A(1);//析構函數就可以顯示調用p1->~A();operator delete(p1);return 0;
}
特別需要理解:malloc/free和new/delete的區別
malloc/free和new/delete:
共同點是:都是從堆上申請空間,并且需要用戶手動釋放。
不同的地方是:
????????1. malloc和free是函數,new和delete是操作符;
????????2. malloc申請的空間不會初始化,new可以初始化;
????????3. malloc申請空間時,需要手動計算空間大小并傳遞,new只需在其后跟上空間的類型即可,如果是多個對象,[]中指定對象個數即可;
????????4. malloc的返回值為void*, 在使用時必須強轉,new不需要,因為new后跟的是空間的類型;
????????5. malloc申請空間失敗時,返回的是NULL,因此使用時必須判空,new不需要,但是new需要捕獲異常;
????????6. 申請自定義類型對象時,malloc/free只會開辟空間,不會調用構造函數與析構函數,而new在申請空間后會調用構造函數完成對象的初始化,delete在釋放空間前會調用析構函數完成空間中資源的清理.
本人實力有限可能對一些地方解釋和理解的不夠清晰,可以自己嘗試讀代碼,或者評論區指出錯誤,望海涵!
感謝大佬們的一鍵三連!?感謝大佬們的一鍵三連!?感謝大佬們的一鍵三連!
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??