【C++】動態記憶體管理
一、C/C++記憶體分佈
我們先回顧在C語言學習階段學習過的一張記憶體分佈圖: 然後我們可以根據上邊的這幅圖,做一下下面這道筆試中一定會遇到的判斷儲存區的筆試題:
int globalVar = 1;
static int staticGlobalVar = 1;
void Test() {
static int staticVar = 1;
int localVar = 1;
int num1[10] = {1, 2, 3, 4};
char char2[] = "abcd";
char* pChar3 = "abcd";
int* ptr1 = (int*)malloc(sizeof (int)*4);
int* ptr2 = (int*)calloc(4, sizeof(int));
int* ptr3 = (int*)realloc(ptr2,sizeof(int)*4);
free (ptr1);
free (ptr3);
}
//選擇題
選項: A.棧 B.堆 C.資料段(全域性區、靜態區) D.程式碼段(常量區)
globalVar在哪裡?__C__ staticGlobalVar在哪裡?_C__
staticVar在哪裡?__C_ localVar在哪裡?_A__
num1 在哪裡?_A__ char2在哪裡?__A_
* char2在哪裡?_A_ pChar3在哪裡?__A_
*pChar3在哪裡?_D__ ptr1在哪裡?__A_
*ptr1在哪裡?__B_
//填空題
sizeof(num1) = _40_;//sizeof陣列名是求陣列的大小
sizeof(char2) = _5__; strlen(char2) = __4_;
sizeof(pChar3) = _4|8_; strlen(pChar3) = _4_;
sizeof(ptr1) = _4|8_; sizeof(ptr2) = __4|8_;
//4代表在32位的程序空間中,8代表在64位的程序空間中
各段記憶體說明:
非靜態區域性變數/函式引數/返回值等儲存在棧中,棧是向下增長的
記憶體對映段是高效的I/O對映方式,用於裝載一個共享的動態記憶體庫。使用者可使用系統介面建立共享共享記憶體,做程序間通訊
堆用於程式執行時動態記憶體分配,堆是向上增長的
資料段儲存全域性資料和靜態資料
程式碼段儲存可執行的程式碼和只讀常量
二、回顧C語言中的動態記憶體管理
動態記憶體管理
就是在堆上動態的分配和釋放空間
。C語言中動態分配的方式是malloc、realloc、calloc、free
四個函式,前三個函式負責分配和調整空間,而free則負責釋放前三個函式分配的空間。
1. malloc/calloc/realloc的區別是什麼?
void *malloc(size_t size);
void *calloc(size_t nmemb, size_t size);
void *realloc(void *ptr, size_t size);
void free(void *ptr);
malloc
:malloc函式用來分配堆上的空間,按照位元組為單位
calloc
:calloc函式也是用來開闢堆上的空間,但和malloc的區別是它把這塊空間初始化為0,也是按照位元組為單位。nmemb指的是分配的位元組數,size指的是每個成員佔的位元組數
realloc
:realloc函式是用來調整已開闢的空間,調整方法為:如果該空間後邊的空間足夠擴大,則直接把後邊的空間續上,源指標不變;如果該空間後邊的空間不夠擴大,則在記憶體中另外找一塊空間,源指標改變指向新的空間,並且自動的將源空間free
free
:free函式用來釋放前三個函式開闢的空間
2. 32位平臺指標為什麼是4個位元組?
在32位的機器上一個程序的地址空間也是32位
的,那麼一個虛擬地址就是由32個0、1串構成的,一個位元組是8個位元位,那麼一個地址就得用4個位元組表示(32/8=4)。64位平臺也是一樣的道理,指標大小為8個位元組。
3. 如何malloc一個大於3G的空間?
在一個32位的程序地址空間下,我們最多可以在堆上開闢的空間小於3G,因為有1G是系統核心的。我們要想在堆上申請到大於3G的空間可以考慮把執行這個程序的平臺換成64位
的,在64位的地址空間就可以申請到大於3G的堆空間。
三、C++中的記憶體管理
C++的語法是相容C語言的語法,所以C語言中的malloc、realloc、calloc、free在C++中都適用
。但是C++中由於記憶體分配失敗的處理方法和C語言不一樣
,C++處理失敗的方法是拋異常
,而C語言處理失敗的方法是返回NULL
,所以C++有自己獨特的記憶體管理方法,既new、delete。
1. new/delete操作內建型別用法
int main()
{
int* ptr1 = new int;//動態申請一個int型別的空間
int* ptr2 = new int(2);//動態申請一個int型別的空間並初始化為2
int* ptr3 = new int[3];//動態申請3個int型別的空間
int* ptr4 = new int[3]();//動態申請3個int型別的空間並初始化為0
//int* ptr5 = new int[3](1);不能這麼初始化
delete ptr1;
delete ptr2;
delete[] ptr3;
delete[] ptr4;
return 0;
}
注
:申請和釋放單個元素的空間,使用new和delete操作符,申請和釋放連續的空間,使用new[]和 delete[]
2. new/delete操作自定義型別用法
class Date
{
public:
Date()
{}
Date(int year, int month, int day)
:_year(year)
, _month(month)
, _day(day)
{
cout << "date" << this << endl;
}
~Date()
{
cout << "~date" << this << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
//C++中new/delete操作自定義型別
Date* d1 = new Date(2018, 11, 5);
Date* d2 = new Date[10]();//或者Date* d2 = new Date[10]
delete d1;
delete[] d2;
//C語言中malloc/delete操作自定義型別
cout << "------------------------" << endl;
Date* d3 = (Date*)malloc(sizeof(Date));
Date* d4 = (Date*)malloc(sizeof(Date)* 10);
free(d3);
free(d4);
return 0;
}
執行上邊的程式,我們可以根據結果發現:在申請自定義型別的空間時,new會呼叫建構函式,delete會呼叫解構函式,而malloc與free不會。
四、operator new和operator delete函式
new和delete是使用者進行動態記憶體申請和釋放的操作符
,operator new 和operator delete是系統提供的全域性函式
,new在底層呼叫operator new全域性函式來申請空間,delete在底層通過operator delete全域性函式來釋放空間
new(操作符)--->呼叫operator new(函式)--->呼叫malloc(函式)--->呼叫構造
//new失敗拋異常(符合C++規範)
//malloc失敗返回NULL
delete(操作符)-->呼叫析構--->呼叫operator delete(函式)--->free(函式)
operator new實際也是通過malloc來申請空間
,如果malloc申請空間成功就直接返回,否則執行使用者提供的空間不足應對措施,如果使用者提供該措施就繼續申請,否則就拋異常
。operator delete最終是通過free來釋放空間的。operator new和operator delete使用者也可以自己實現,使用者實現時即可實現成全域性函式,也可實現成類的成員函式
,但是一般情況下不需要實現,除非有特殊需求。
五、定位new表示式
定位new表示式
是在已分配的原始記憶體空間
中呼叫建構函式初始化一個物件。
使用格式:new (place_address) type或者new (place_address) type(initializer-list)
。place_address必須是一個指標,initializer-list是型別的初始化列表
使用場景:定位new表示式在實際中一般是配合記憶體池使用
。因為記憶體池分配出的記憶體沒有初始化
,所以如果是自定義型別的物件,需要使用new的定義表示式進行顯示調建構函式進行初始化。
class Date
{
public:
Date()
{}
Date(int year, int month, int day)
:_year(year)
, _month(month)
, _day(day)
{
cout << "date" << this << endl;
}
~Date()
{
cout << "~date" << this << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
//d1和d2現在還不是一個物件,因為沒有呼叫建構函式初始化,只能說大小和物件的大小相同
Date* d1 = (Date*)malloc(sizeof(Date));
Date* d2 = (Date*)malloc(sizeof(Date));
//呼叫new的定位表示式初始化
new(d1)Date(2018, 11, 5);//呼叫有參構造
new(d2)Date();//呼叫無參構造
}
六、常見面試題總結
1. malloc/free和new/delete的區別?
共同點:
它們都是在堆上開闢空間,而且都需要手動釋放。
不同點:
malloc/free是函式,而new/delete是操作符,new/delete在底層呼叫了malloc/free
malloc申請的空間不能直接初始化,而new申請的空間會呼叫建構函式,可以直接傳參初始化
malloc申請空間時,需要手動計算空間大小並傳遞,new只需在其後跟上空間的型別即可
malloc的返回值為void*, 在使用時必須強轉,new不需要,因為new後跟的是空間的型別
malloc申請空間失敗時,返回的是NULL,因此使用時必須判空,new不需要,但是new需要捕獲異常
malloc/free只能申請內建型別的空間,不能申請自定義型別的空間,因為其不會呼叫構造與解構函式, 而new可以,new在申請空間後會呼叫建構函式完成物件的構造,delete在釋放空間前會呼叫解構函式 完成空間中資源的清理
malloc申請的空間一定在堆上,new不一定,因為operator new函式可以重新實現
new/delete比malloc/free的效率稍微低點,因為new/delete的底層封裝了malloc/free,存在函式呼叫的開銷
2. 請設計一個類,該類只能在堆上建立物件
將類的建構函式私有,拷貝構造宣告成私有
。防止別人呼叫拷貝在棧上生成物件。
提供一個靜態的成員函式,在該靜態成員函式中完成堆物件的建立
#include<iostream>
using namespace std;
class HeapType
{
public:
static HeapType* CreateObj()
{
return new HeapType;
}
void Print()
{
cout << "HeapType:" << this << endl;
}
private:
HeapType()//建構函式私有化
{}
//防拷貝
HeapType(HeapType const&);
// HeapType& operator=(HeapType const&);
};
int main()
{
HeapType::CreateObj()->Print();
HeapType::CreateObj()->Print();
//HeapType ht; 這樣不行,因為構造器是私有的,所以滿足條件,不在棧上
//HeapType ht1(HeapType::CreateObj());//拷貝構造也不行
return 0;
}
3. 請設計一個類,該類只能在棧上建立物件
class StackType
{
public:
StackType()
{}
void Print()
{
cout << "StackType:" << this << endl;
}
private:
//不能再堆上建立,我們可以考慮直接把operator new和new的定位表示式宣告為私有的
//將operator new宣告為私有的,就把new的定位表示式也宣告為私有的了
void* operator new(size_t size);
void operator delete(void* p);
};
int main()
{
StackType st1;//ok
st1.Print();
//StackType st2 = new StackType();//不行,因為operator new被遮蔽了
//StackType* st3 = (StackType*)malloc(sizeof(StackType));
//new(st3)StackType();//不行,因為new的定位表示式也被遮蔽
return 0;
}