關於C++的內存管理, 以及C++當中的 operator new和operator delete函數, 定位new表達式

在C語言當中, 動態內存的管理方式分爲 malloc / calloc / realloc 和 free

  1. malloc函數可以在堆上申請指定字節的內存空間, 申請成功返回申請到的空間的首地址, 如果申請失敗會返回NULL.

    void* malloc(size_t size);
    注意malloc申請到的空間是沒有經過初始化的

  2. calloc函數與malloc的功能相似, 但是calloc申請到的空間是經過初始化的, 初始化內容爲0.

     void* malloc(size_t, num, size_t size);//申請num個字節大小爲size的空間
    
  3. realloc函數

     void* realloc (void* ptr, size_t size);
    

    realloc函數會改變ptr所指向空間的內存大小, 其中ptr必須是指向堆內存空間的指針, 也就是由malloc/calloc/realloc函數所分配空間的指針. 如果size小於或等於之前ptr所指向空間的大小, 就會保持原來狀態不變, 如果size大於之前ptr所指向空間的大小, 會重新開闢一塊大小爲size的內存空間, 並將原來所指向空間中的內容複製到新的空間上. realloc函數所申請的空間也是沒有經過初始化的.

C語言內存管理方式在C++當中仍然可以繼續使用, 但有些地方用起來並不方便, 比如開闢自定義類型的空間, 因此在C++當中又提出了自己的誒存管理方式, 通過new和delete操作符進行動態內存管理.

new / delete對於內置類型的操作

內置類型就是 int, char, float, double等

void test()
{
	//動態申請一個int類型的空間
	int* pi = new int;//申請一個4字節整型空間

	//動態申請一個int類型的空間並初始化
	int* pi2 = new int(2);//申請一個4字節整型空間並賦值爲2

	//動態申請int類型的數組
	int* pi3 = new int[5];//申請了一個長度爲5的整型數組

	delete pi;
	delete pi2;
	delete[] pi3;
}

注意:

  1. 注意申請空間時 () 和 [] 的區別, ()是將申請到的空間初始化, 而 [] 是申請數組時用的;

  2. 對應的釋放空間時, 釋放連續空間(即數組)時記得要用到 delete[].

  3. 連續使用不被允許, 做不到申請一片連續空間並初始化裏面的值

     int* pi4 = new int[5](1);//這樣寫是不可以的
    

在這裏插入圖片描述
其實看到這裏, 我們就會想, 在C語言當中已經有了 malloc / calloc / realloc / free這些函數, 那爲什麼在C++當中又要出現 new / delete, new [] / delete[]?

 首先, 針對內置類型來說, 無論使用 new 還是 malloc 等其實都是一樣的.
 對於自定義類型來說, 就會不一樣了. 比如下面的例子:

自定義類型 A

class A
{
public:
	A(int x = 10) :
		_val(x)
	{
		cout << "A()" << endl;
	}

	~A()
	{
		cout << "~A()" << endl;
	}
private:
	int _val;
};

用malloc申請空間

void test2()
{
	A* pA_C = (A*)malloc(sizeof(A));
	cout << pA_C << endl;
	cout << pA_C->getVal() << endl;
	free(pA_C);
}

輸出結果:

在這裏插入圖片描述
空間申請成功, 類成員無法初始化打印隨機值.
接下來使用 new / delete

void test1()
{
	A* pA_CPP = new A;//申請單個A類型的對象
	cout << pA_CPP << endl;
	cout << pA_CPP->getVal() << endl;
	delete pA_CPP;//釋放掉這片空間
}

輸出結果:
在這裏插入圖片描述
可以很清楚的看到, new在針對自定義類型申請空間的時候, 會自動調用構造函數, 構造函數又幫我們完成了對類成員_val的初始化, 而delete會自動調用析構函數完成資源的清理工作.

我們可以進一步驗證一下, new / delete 針對自定義類型的操作, 接下來我們申請一個A類型的對象數組

// new / delete
A* pA_CPP_ARR = new A[10];
delete[] pA_CPP_ARR;

在這裏插入圖片描述
調用10次構造函數, 10次析構函數

// malloc
A* pA_C_ARR = (A*)malloc(sizeof(A)* 10);
free(pA_C_ARR);

在這裏插入圖片描述
malloc只開闢空間.

結論: 建議在C++當中使用 new / delete

operator new和operator delete函數

看到operator我們一定會想到運算符重載, 某種角度來說這樣理解也可以, 但是這裏我們要儘量將其看做一個整體, 看做函數來理解它.

同樣, 先給一個自定義類型

class A
{
public:
	A(int x = 10) :
		_val(x)
	{
		cout << "A()" << endl;
	}

	~A()
	{
		cout << "~A()" << endl;
	}

	int getVal()
	{
		return _val;
	}
private:
	int _val;
};

接下來申請空間

void test1()
{
	A* ptr1 = (A*)malloc(sizeof(A));
	A* ptr2 = new A(1);
	A* ptr3 = (A*)operator new(sizeof(A));


	free(ptr1);
	delete ptr2;
	operator delete(ptr3);
}

執行結果

在這裏插入圖片描述
均申請空間成功, 通過上面的講解我們知道, new / delete 會調用構造和析構, 那麼operator new / operator delete 又是幹什麼用的呢?它與malloc / free的區別在哪裏呢?

  1. 首先operator new / operator delete的使用與 malloc / free相同

     void* operator new(size_t size);
     operator delete(void* ptr);
    
  2. 舉個例子來看operator new與malloc的區別

     //用malloc去申請空間
     size_t size = 2;
    
     void* ptr1 = malloc(size * 1024 * 1024 * 1024);
     cout << ptr1 << endl;//申請失敗, 返回NULL(0)
    

在這裏插入圖片描述

	//用operator new去申請空間
	void* ptr2 = operator new(size * 1024 * 1024 * 1024);
	cout << ptr2 << endl;

在這裏插入圖片描述
程序會崩掉. 因爲operator new對比malloc, operator new申請空間失敗會拋異常(面向對象的錯誤處理方式), 必須跳到跳到catch的地方, 所以代碼要修改爲下面這樣

try
{
	void* ptr2 = operator new(size * 1024 * 1024 * 1024);
	cout << ptr2 << endl;
}
catch (exception& e)
{
	cout << e.what() << endl;
}

運行結果如圖:
在這裏插入圖片描述
總結: operator new 對比 malloc 使用方式相同, 處理錯誤的方式不同

new本身就是爲了C++面向對象編程而產生的, 因此從malloc->operator new->new是層層遞進的

malloc
operator new ==> malloc + 申請空間失敗拋異常的實現
new          ==> operator new + 自動調用構造函數

對應的擇優free, operator delete, delete
而delete 比起 free 不一樣的地方就是 delete 自調用析構函數清理資源.
operator delete 與 free沒有區別.因爲釋放空間失敗會自動終止程序.

new / delete原理

new原理

  1. 調用 operator new函數申請空間
  2. 在申請到的空間上執行自定義類型的構造函數, 完成對象的構造

delete原理

  1. 在申請到的空間上執行自定義類型的析構函數, 完成資源的清理工作
  2. 調用operator delete函數釋放對象的空間

new T[N]原理

  1. 實際調用operator new函數申請N個對象的空間
  2. 在申請到的空間執行N次自定義類型的構造函數

delete[N]原理

  1. 在申請到的空間上執行N次自定義類型的析構函數, 完成N個對象的資源清理
  2. 實際調用 operator delete函數釋放空間

定位new表達式

定位new表達式就是在已分配的內存空間上調用構造函數初始化一個對象

比如:

class Test
{
public:
	Test(int x = 10) :
		_a(x)
	{
		cout << "Test" << endl;
	}

	int GetVal()
	{
		return _a;
	}

	~Test()
	{
		cout << "~Test()" << endl;
	}

private:
	int _a;
};

void test3()
{
	Test* pT = (Test*)malloc(sizeof(Test));//申請好的內存

	new(pT) Test(10);//定位new表達式

	cout << pT->GetVal() << endl;
}

總結

malloc / free和new / delete的區別

  1. malloc / free是函數, new / delete是操作符
  2. malloc申請空間不會初始化, new可以(自動調用構造函數)
  3. malloc函數使用時需要手動計算類型大小(sizeof(T)), new不需要, 直接在new後面跟所需空間類型即可
  4. malloc函數申請空間返回值爲void*, 因此使用時要做類型強轉, new不需要
  5. malloc申請失敗返回NULL(0), new申請失敗拋異常
  6. 最重要的針對自定義類型, malloc / free只申請和釋放空間, new會自動調用構造函數完成對象的初始化, delete會調用自定義類型的析構函數完成資源清理, 再釋放空間.
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章