【C++基礎】C/C++動態內存管理


C/C++中,有時候不能確定數組該定義為多大,這時候程序需要從系統中動態的獲得內存空間,所謂動態內存分配,是指在程序執行的過程中動態地分配或者回收存儲空間的分配內存的方法,由系統根據程序的需要即時分配,且分配的大小就是程序要求的大小

與之相反的是靜態內存分配,例如數組,在編譯期間就已經分配好內存了

C語言動態內存分配我已經總結過了,博客鏈接如下:

C語言動態內存分配總結

在這里主要總結C++新加入的動態內存管理

一、C++動態內存管理

C++通過new和delete動態管理內存,new/delete動態管理對象,new[]/delete[]動態管理對象數組

void Test() {
	int* p1 = new int;
	int* p2 = new int(3);
	int* p3 = new int[3];

	delete p1;
	delete p2;
	delete[] p3;
}

如上代碼,p1表示動態的分配一個int類型大小的內存空間

p2是分配了一個int類型大小的內存空間,並且初始化為3

p3是動態的分配了三個大小為int類型的內存空間

注意,和malloc/free一樣,new/delete和new[]/delete[]也必須成對的使用,不然就會造成內存泄露

使用delete需要注意,只能用delete來釋放用new申請的內存空間,然而,對空指針使用delete是安全的

使用new和delete應遵守如下規則:

1.不要使用delete來釋放不是new分配的內存

2.不要使用delete釋放一個內存兩次

3.如果使用new[]來為數組申請內存,那么必須使用delete[]來釋放

4.如果使用delete為一個實體申請內存,則必須使用delete來釋放

5.對空指針使用delete是安全的

如果使用不匹配會如何呢?暫且按下不表,會在后文解釋

二、new/delete和malloc/free的區別

new返回制定類型的指針,並且自動計算所需要的大小,還可以選擇對申請的內存進行初始化

int* p1 = new int; //返回一個指向int類型內存空間的指針
int* p2 = new int(3);//返回一個指向int類型內存空間的指針,*p2的值為3
int* p4 = (int*)malloc(sizeof(int));//返回一個指向int類型內存空間的指針

如上,malloc需要我們手動的傳入需要申請的內存空間的大小,並且在返回后強轉為實際類型的指針

而且malloc只管分配內存,並不能對內存空間進行初始化,所以得到一片新內存中,其值將是隨機的(calloc可以初始化)

malloc和free是c/c++的標准庫函數,new/delete是運算符

對於非內部數據類型的對象而言,光有用malloc和free無法滿足要求,對象在創建的時候要自動執行構造函數,在消亡之前要自動執行析構函數。malloc/free不是運算符,不在編譯器的控制權限之內,不能把析構函數和構造函數的任務強加於malloc/free

如下代碼:

class Array{
public:
	Array(int size = 10)
	:_a(0)
	,_size(10){
		cout << "Array" << endl;
		if (size > 0) {
			_a = new int[size];
		}
	}
	Array(const Array& a) {
		cout << "拷貝構造函數" << endl;
	}
	~Array() {
		cout << "~Array" << endl;
		delete[] _a;
		_a = NULL;
		_size = 0;
	}
private:
	int* _a;
	int _size;
};

有這么一個類,我們用不同動態內存分配方式來創建它,如下:

  Array* p1 = (Array*)malloc(sizeof(Array));

    Array* p2 = new Array;
    Array* p3 = new Array(20);
    Array* p4 = new Array[10];

    free(p1);//沒有調用析構函數
    delete p2;
    delete p3;
    delete[] p4;

如上代碼,p1產生的時候,使用malloc和free並沒有為對象調用構造函數和析構函數,所以我們要使用new/delete來為對象申請內存空間

三、代碼中的內存分配方式


1.從數據段(靜態存儲區域)分配,內存在編譯期間就已經分配好了,這塊內存在程序的整個運行區間都存在,如全局變量,static變量

2.在棧上分配,執行函數時,函數內部的局部變量都是在棧上分配內存,函數執行結束時這些存儲單元自動被釋放,棧上分配內存的效率高,但是棧的空間不大

3.從堆上分配,也就是動態內存分配,程序運行時用malloc和new申請任意多少內存,程序員自己負責在何時free或者delete釋放內存。動態內存的生存期由程序員決定,使用靈活,但是如果在堆上申請了空間,就有責任去還給堆,否則就會出現內存泄露。頻繁的分配和釋放不同大小的堆空間將會產生堆內碎塊

在程序中,基本就是這三種內存分配的方式

四、C++中其他內存管理接口

C++中海油其他的內存管理接口,如:

void* operator new(size_t size);
void  operator delete(size_t size);
void* operator new[](size_t size);
void  operator delete[](size_t size);

注意,上面的函數並不是重載new/delete表達式,我們並不能重定義new/delete

operator和malloc/free用法一樣,都是動態的申請內存,但是不調用對象的構造函數和析構函數

operator有三種更具體的形式

//異常拋出形式
void* operator new(std::size_t size)throw(std::bad_alloc);
//不拋出異常形式
void* operator new (std::size_t size, const std::nothrow_t& nothrow_value) throw();  
//placement形式
void* operator new(std::size_t size, void* ptr)throw();

operator new 和 operator delete 只是malloc和free的一層封裝

使用new的時候,編譯器會執行兩步操作:

1.調用::operator new()分配計算的內存

2.在分配好的內存上使用該類型的構造函數進行初始化

delete剛好相反:

1.調用對象的析構函數

2.使用::operator delete()釋放內存

new和delete底層實現的這兩個步驟是無法改變的,由於new和delete是操作符,所以實現形式上不能由程序員去控制更改,但是operator new() 和 operator delete()是屬於std中的函數,作用於malloc和free類似,我們可以按照需要去重載這兩個函數,注意,是重載函數而不是操作符重載

雖然我們不能改變new/delete的行為,但是我們可以重載operator new() 和 operator delete() 這兩個函數來實現自己想要的內存管理方式

關於operator new() 有幾點要注意:

1.當無法滿足所要求分配的空間是,

    (1)如果有new_handler,則調用new_handler,如果沒有要求不拋出異常,則執行bad_alloc 異常,否則返回0

    (2)重載時,返回類型必須聲明為void*

    (3)重載時,第一個參數類型必須為表達要求分配空間的大小(字節),類型為size_t

    (4)重載時,可以帶其它參數

五、定位new表達式(replacement版本)

new運算符還有一種變體,被稱為定位new運算符,它讓你能夠指定要使用的位置,我們可以使用這種特性來設置其內存管理規程,處理需要通過特定地址訪問的硬件或在特定位置創建對象

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

new(place_address)type;

new(place-address)type(initializer-list)

place_address必須是一個指針,initializer-list是類型的初始化列表

還是上面的Array類,代碼如下:

//malloc和free+定位操作符new()顯示調用析構函數,模擬new和delete的行為
Array* p1 = (Array*)malloc(sizeof(Array));
new(p1)Array(100);
p1->~Array();//調用析構函數
free(p1);//釋放申請的內存空間

// 1.malloc/free + 多次調用定位操作符new()/顯示調用析構函數,模擬 new[]和delete[] 的行為     
Array* p2 = (Array*) malloc(sizeof (Array)*10);     
for(int i = 0; i < 10; ++i ) {         
	new(p2 +i) Array;    
}
for (int i = 0; i < 10; ++i) { 
	p2[i].~Array(); 
}     
free(p2);

定位new表達式不能調用delete刪除 placement new的對象,需要人為的調用對象的析構函數,並且人為的釋放掉占用的內存。

六、深度剖析深度剖析new/delete & new[]/delete[]

new/delete的底層機制:


new:先調用operator new()(這個函數底層的實現機制是malloc函數),然后再調用構造函數,再return一個ptr指針

delete:先調用析構函數,再調用operator delete()函數(底層實現機制是free函數)

new[]/delete[]的底層機制:


new[]:先是調用operator new[]()函數,然后再調用count次構造函數構造,再返回指針ptr

delete[]是調用count次析構函數,再調用operator delete[]()

注意,為什么傳入的是size+4,如圖:


在申請的時候回多申請四個字節存儲對象個數

介紹完底層機制,我們就可以理清上面的遺留問題:如果不成對使用會如何?

例如:new[]申請了一段內存空間,然后返回的指針ptr是指向count后面的,不是申請的這段內存空間的開頭,所以如果我們使用free()來釋放的話就導致錯誤,因為ptr不是指向申請的內存空間的開始位置

所以一定得成對使用





注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



c/c++ 動態內存管理(一) C/C++動態內存管理 C/C++動態內存管理 c++動態內存管理 c++動態內存管理 C++動態內存管理 c++動態內存管理 c++動態內存管理 C++動態內存管理 C++中動態內存的管理
 
粤ICP备14056181号  © 2014-2021 ITdaan.com