C++ 動態內存管理:c/c++的動態內存管理,new/delete,operator new/delete,placement-new, 內存泄漏


c/c++的動態內存管理

在開始之前首先要了解c和c++的內存分佈,我簡單的畫了一個圖
在這裏插入圖片描述

  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;
}

接下來先進入調試,看看他們有什麼區別
在這裏插入圖片描述
可以看到d1雖然創建了,但裏面都是隨機值,而d2自動調用了默認的構造函數。

然後再看看free和delete
在這裏插入圖片描述
在這裏插入圖片描述
調用後會發現他們都會清除數據,但是delete會再調用一次析構函數。

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

在這裏插入圖片描述
這裏可以看到,它們的基本操作類似,只是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、事後查錯型。如泄漏檢測工 具
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章