1. 程式人生 > >【C++】動態記憶體管理

【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的區別?

共同點

  1. 它們都是在堆上開闢空間,而且都需要手動釋放。

不同點

  1. malloc/free是函式,而new/delete是操作符,new/delete在底層呼叫了malloc/free
  2. malloc申請的空間不能直接初始化,而new申請的空間會呼叫建構函式,可以直接傳參初始化
  3. malloc申請空間時,需要手動計算空間大小並傳遞,new只需在其後跟上空間的型別即可 malloc的返回值為void*, 在使用時必須強轉,new不需要,因為new後跟的是空間的型別
  4. malloc申請空間失敗時,返回的是NULL,因此使用時必須判空,new不需要,但是new需要捕獲異常
  5. malloc/free只能申請內建型別的空間,不能申請自定義型別的空間,因為其不會呼叫構造與解構函式, 而new可以,new在申請空間後會呼叫建構函式完成物件的構造,delete在釋放空間前會呼叫解構函式 完成空間中資源的清理
  6. malloc申請的空間一定在堆上,new不一定,因為operator new函式可以重新實現
  7. 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;
}