天天看點

C++ 動态記憶體管理:c/c++的動态記憶體管理,new/delete,operator new/delete,placement-new, 記憶體洩漏 c/c++的動态記憶體管理 new/delete opeartor new/delete placement-new記憶體洩漏(引用)

  • c/c++的動态記憶體管理
  • new/delete
  • opeartor new/delete
  • placement-new
  • 記憶體洩漏

c/c++的動态記憶體管理

在開始之前首先要了解c和c++的記憶體分布,我簡單的畫了一個圖

C++ 動态記憶體管理:c/c++的動态記憶體管理,new/delete,operator new/delete,placement-new, 記憶體洩漏 c/c++的動态記憶體管理 new/delete opeartor new/delete placement-new記憶體洩漏(引用)
  1. 棧又叫堆棧,非靜态局部變量/函數參數/傳回值等等,棧是向下增長的。
  2. 記憶體映射段是高效的I/O映射方式,用于裝載一個共享的動态記憶體庫。使用者可使用系統接口建立共享共 享記憶體,做程序間通信。
  3. 堆用于程式運作時動态記憶體配置設定,堆是可以上增長的。
  4. 資料段–存儲全局資料和靜态資料。
  5. 代碼段–可執行的代碼/隻讀常量。

之前我寫過一篇關于c語言動态記憶體管理的部落格:

https://blog.csdn.net/qq_35423154/article/details/103283679

這次再将這個問題引申到c++上,談一談c++對c動态記憶體管理的擴充和改進。

首先,我們需要找到,new/delete與c語言的malloc/free之間有什麼差別,為什麼有了malloc和free還要實作new和delete呢?new和delete又在malloc和free上有什麼改進呢?

new/delete

首先來簡單介紹一下new和delete的用法

int main()
{
	int* data1 = new int;
	//動态配置設定一個int的空間

	int* data2 = new int(15);
	//動态配置設定一個int的空間,并将它初始化成15

	int* arr = new int[10];
	//動态配置設定具有10的int的空間

	//這裡需要注意[]中寫的是該類型資料的個數,而()是初始化的内容

	delete data1;
	delete data2;

	delete[] arr;
	//對于單個資料直接用delete加上名字,如果是多個則需要在delete後面加上[]
}
           

對于内置類型,malloc/free和new/delete的用法類似,功能也差不多,下面來試試自定義類型。

class Date
{
public:
	Date(int year = 2020, int month = 4, int day = 28)
		:_year(year),
		 _month(month),
		 _day(day)
	{
	}

	~Date()
	{
		cout << "調用析構函數" << endl;
	}

private:
	int _year;
	int _month;
	int _day;	
};


int main()
{


	Date* d1 = (Date*)malloc(sizeof(Date));
	//使用c語言的malloc和free
	
	Date* d2 = new Date();
	//使用c++的new和delete
	
	free(d1);
	delete(d2);

	return 0;
}
           

接下來先進入調試,看看他們有什麼差別

C++ 動态記憶體管理:c/c++的動态記憶體管理,new/delete,operator new/delete,placement-new, 記憶體洩漏 c/c++的動态記憶體管理 new/delete opeartor new/delete placement-new記憶體洩漏(引用)

可以看到d1雖然建立了,但裡面都是随機值,而d2自動調用了預設的構造函數。

然後再看看free和delete

C++ 動态記憶體管理:c/c++的動态記憶體管理,new/delete,operator new/delete,placement-new, 記憶體洩漏 c/c++的動态記憶體管理 new/delete opeartor new/delete placement-new記憶體洩漏(引用)
C++ 動态記憶體管理:c/c++的動态記憶體管理,new/delete,operator new/delete,placement-new, 記憶體洩漏 c/c++的動态記憶體管理 new/delete opeartor new/delete placement-new記憶體洩漏(引用)

調用後會發現他們都會清除資料,但是delete會再調用一次析構函數。

下面來看看他們的彙編有什麼不同

C++ 動态記憶體管理:c/c++的動态記憶體管理,new/delete,operator new/delete,placement-new, 記憶體洩漏 c/c++的動态記憶體管理 new/delete opeartor new/delete placement-new記憶體洩漏(引用)

這裡可以看到,它們的基本操作類似,隻是new和delete多調用了構造函數和析構函數,但是new還調用了一個operator new,那又是什麼呢?

opeartor new/delete

這裡我找來了operator new和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
			// throw a bad_alloc exception
			static const std::bad_alloc nomem;
			_RAISE(nomem);
		}
	return (p);
}
           
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;
}
           

我們可以看到, opeartor new其實底層調用的還是malloc,但是它改進了一些東西,比如c中配置設定失誤時傳回NULL,而它這裡是抛出一個異常,operator和free也類似,于是我百度了一下,了解他們為什麼要這樣實作。

我得到的結果是,因為c是面向過程,c++是面向對象,這兩個函數其實就是從面向過程到面向對象之間的一種過渡,增添了異常和一些内容,再從opeartor new/delete到new/delete又增添了調用構造函數和析構函數,更加符合面向對象的思想。

總結

調用new/delete其實底層會這樣實作。

1.動态配置設定空間/釋放空間(調用operator new/delete->調用malloc/free)

2.自定義類型(調用構造函數/析構函數)

如果是 new[]/delete[]

則配置設定一段連續的空間,并且調用n次 構造函數/析構函數

内置類型:

對于内置類型,new/delete其實和malloc/free基本類似,隻不過new和delete失敗後會抛出一個bad_allocy異常,而malloc/free會傳回一個NULL。

同時new會初始化建立的資料

自定義類型:

1.調用operator new/delete 配置設定空間/釋放空間

2.調用構造函數/析構函數

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在釋放空間前會調用析構函數完成空間中資源的清理

placement-new

placement-new也就是定位new表達式,是在已配置設定的原始記憶體空間中調用構造函數初始化一個對象。這一步其實就是new再調用operator後再執行的那一步。

在實際中我們也會經常使用到定位new表達式,因為記憶體池配置設定出來的記憶體沒有初始化,是以如果是自定義類型的對象,我們需要使用定位new表達式來顯式調用構造函數來為其初始化。

下面介紹一下用法

使用格式:

new (place_address) type或者new (place_address)

type(initializer-list) place_address必須是一個指針,initializer-list是類型的初始化清單

int main()
{
	Date* d1 = (Date*)malloc(sizeof(Date));
	new(d1) Date;
	//對于剛剛那個日期類,在使用malloc配置設定後再用定位new來顯式調用構造函數。

	return 0;
}
           

記憶體洩漏(引用)

什麼是記憶體洩漏

記憶體洩漏指因為疏忽或錯誤造成程式未能釋放已經不再使用的記憶體的情況。記憶體洩漏并不是指記憶體在實體上的消失,而是應用程式配置設定某段記憶體後,因為設計錯誤,失去了對該段記憶體的控制,因而造成了記憶體的浪費。

記憶體洩漏的危害:

長期運作的程式出現記憶體洩漏,影響很大,如作業系統、背景服務等等,出現記憶體洩漏會導緻響應越來越慢,最終卡死。

一般來說,記憶體洩漏大多數存在于c/c++程式中,因為現在的主流語言如java,python,c#等都具有完善的垃圾回收機制,是以一般不會存在記憶體洩漏的情況,但也因為這種上述語言存在這種垃圾回收機制,是以在回收記憶體的時候也會花費寶貴的CPU資源,導緻速度有所下降,是以對于c和c++,這是一把雙刃劍,全靠程式員如何掌控。

C/C++程式中一般我們關心兩種方面的記憶體洩漏:
  • 堆記憶體洩漏(Heap leak) 堆記憶體指的是程式執行中依據須要配置設定通過malloc / calloc / realloc / new等從堆中配置設定的一塊記憶體,用完後必須通過調用相應的 free或者delete

    删掉。假設程式的設計錯誤導緻這部分記憶體沒有被釋放,那麼以後這部分空間将無法再被使用,就會産生Heap Leak。

  • 系統資源洩漏 指程式使用系統配置設定的資源,比方套接字、檔案描述符、管道等沒有使用對應的函數釋放掉,導緻系統資源的浪費,嚴重可導緻系統效能減少,系統執行不穩定。

如何避免記憶體洩漏

  1. 工程前期良好的設計規範,養成良好的編碼規範,申請的記憶體空間記着比對的去釋放。ps:這個理想狀 态。但是如果碰上異常時,就算注意釋放了,還是可能會出問題。需要下一條智能指針來管理才有保 證。
  2. 采用RAII思想或者智能指針來管理資源。
  3. 有些公司内部規範使用内部實作的私有記憶體管理庫。這套庫自帶記憶體洩漏檢測的功能選項。
  4. 出問題了使用記憶體洩漏工具檢測。ps:不過很多工具都不夠靠譜,或者收費昂貴。 總結一下: 記憶體洩漏非常常見,解決方案分為兩種:1、事前預防型。如智能指針等。2、事後查錯型。如洩漏檢測工 具

繼續閱讀