1. 程式人生 > >C/C++:記憶體管理

C/C++:記憶體管理

一.C/C++中程式記憶體區域劃分

在C/C++中,程式記憶體區域可以分成四個部分:

1.棧:用來儲存非靜態的區域性變數/函式引數/返回值等等,棧試想下增長的    

2.堆:用於程式執行時的動態記憶體分配,堆是可以向上增長的

3.資料段:儲存全域性資料和靜態資料

4.程式碼段:儲存可執行的程式碼和只讀的常量

二.C語言動態記憶體的管理方式

C語言我們使用的開闢記憶體空間的函式有三個:

1.malloc: void *malloc(size_t size);                              用來申請size個位元組的空間

2.calloc: void *calloc(size_t nmemb, size_t size);        用來申請size*nmemb個位元組的空間,並對記憶體初始化

3.realloc: void *realloc(void *ptr, size_t size);              如果size小於或者等於ptr指向的空間,則保持不變,否則重新分配記憶體空 間,把之                                                                                        前的資料搬移到引得空間中

所以:relloc(NULL,size)=malloc(NULL,size)

用來記憶體釋放的只有一個函式:

free:  void free(void *ptr);

void test()
{
	int* p1 = (int *)malloc(sizeof(int)* 10);
	int* p2 = (int *)calloc(4,sizeof(int));
	int* p3 = (int *)realloc(p2,sizeof(int)* 6);
	free(p1);
	free(p3);
	//free(p2);在這不應該重複釋放,因為p3就是在p2的地址上多申請sizeof(int)* 6個位元組,不能重複釋放
}

我們在用malloc申請的空間一定要記住:要用free釋放,不然就會記憶體洩漏。

當我們沒有釋放記憶體我們可以通過系統給的函式自己檢測:

#include<crtdbg.h>

CrtDumpMemoryLeaks();

假如沒有記憶體釋放,就會顯示如下:

三.C++的動態記憶體管理方式

C語言的動態記憶體的管理方式在C++中同樣適用,只不過C++有自己的動態記憶體管理方式:C++中通過new和delete來進行動態管理。

new/delete動態管理物件

newp[]/delete[]動態管理物件陣列

void test()
{
	int* p1 = new int;       分配一個int的單個數據空間
	int* p2 = new int(3);    分配一個int的單個數據空間,(3)是初始化的值
	int*p3 = new int[3];     分配三個int的資料空間
	delete p1;
	delete p2;
	delete[] p3;
}

在C++中:new和delete,new[]和delete[]一定要匹配使用!一定要匹配使用!!一定要匹配使用!!!

 

malloc/free和new/delete的比較

看程式碼:

void test()
{
	int* p1 = (int*)malloc(sizeof(int));
	int* p2 = (int*)malloc(sizeof(int));
	int* p3 = new int;
	int* p4 = new int;
	int* p5 = new int[10];
	int* p6 = new int[10];

	delete p1;
	delete[] p2;

	free(p3);
	delete[]p4;

	free (p5);
	delete p6;
}

我們分別用malloc和new申請空間,但是在釋放的時候,卻用的其他兩種釋放的形式。那麼這樣的程式碼會出錯嗎?有沒有記憶體洩漏?

通過除錯,我們發現程式碼不僅不會出錯,而且並沒有出現記憶體洩漏。

那麼我們在加一個類再來除錯:

class Test
{
public:
	Test()
             :_data(0)
	{
		cout << this << endl;
	}
	~Test()
	{
		cout << this << endl;
	}
private:
	int _data;
};
void test()
{
	Test* p1 = (Test*)malloc(sizeof(Test));
	Test* p2 = (Test*)malloc(sizeof(Test));
	Test* p3 = new Test;
	Test* p4 = new Test;
	Test* p5 = new Test[10];
	Test* p6 = new Test[10];

	delete p1;
	delete[] p2;

	free(p3);
	delete[]p4;

	free (p5);
	delete p6;
}
int main()
{
	test();
	_CrtDumpMemoryLeaks();
	system("pause");
	return 0;
}

當我們在執行的時候發現每當遇到delete[]的時候程式碼就會觸發斷點二崩潰。

我們按照正常的釋放方式執行一遍:

class Test
{
public:
	Test()
	{
		cout << this << endl;
	}
	~Test()
	{
		cout << this << endl;
	}
private:
	int _data;
};
void test()
{
	Test* p1 = (Test*)malloc(sizeof(Test));
	Test* p2 = (Test*)malloc(sizeof(Test));
	Test* p3 = new Test;
	Test* p4 = new Test;
	Test* p5 = new Test[3];
	Test* p6 = new Test[3];
        
        printf("\n");
        
	free(p1);
	free(p2);
	//delete[] p2;

	delete(p3);
	delete p4;
	//delete[]p4;

	delete[]p5;
	delete[]p6;
	//free (p5);
	//delete p6;
}
int main()
{
	test();
	_CrtDumpMemoryLeaks();
	system("pause");
	return 0;
}

執行結果:

所以在C++之所以加入了new和delete是因為它們分別需要呼叫建構函式和解構函式。

四.new和delete的實現原理

1.記憶體型別

new和delete申請的是單個元素的空間,而new[]和delete[]申請的是一段連續的空間。new在申請空間失敗時會丟擲異常,而malloc會返回空指標。

2.自定義型別

(1)new的原理

呼叫operator new函式申請空間,然後再申請的空間上呼叫建構函式,完成對物件的構造

(2)delete的原理

在申請的空間上呼叫解構函式,清理物件中的資源,然後呼叫operator delete函式釋放物件的空間

(3)new[]的原理

呼叫operator new[]函式,在operator new[]中實際呼叫operator new函式完成N個物件空間的申請,然後在申請的空間上執行N次建構函式,完成對N個物件的構造

(4)delete[]的原理

在釋放的物件空間上執行N次解構函式,完成N個物件中資源的清理 ,再呼叫operator delete[]釋放空間,實際在operator delete[]中呼叫operator delete來釋放空

五.定位new表示式

定位new表示式就是給已經申請好的空間呼叫建構函式來初始化一個物件。

看如下程式碼:我們用malloc申請的空間,用定位new表示式來對物件進行初始化。

class Test
{
public:
	Test()
		:_data(0)
	{
		cout << this << endl;
	}
	~Test()
	{
		cout << this << endl;
	}
private:
	int _data;
};
void test()
{
	//pt現在指向的只不過是與Test物件相同大小的一段空間,
	//還不能算是一個物件,因為建構函式沒有執行   
	Test* pt = (Test*)malloc(sizeof(Test));      
	new(pt) Test;  //定位new表示式的格式
}
int main()
{
	test();
	_CrtDumpMemoryLeaks();
	system("pause");
	return 0;
}

六.malloc/free和new/delete的區別

我們都知道這malloc/free和new/delete都是在堆上申請空間,都需要通過使用者手動進行釋放,但是它們的區別有很多。

1.區別:

(1)malloc/free時函式,而new和delete時操作符

(2)malloc申請的空間不能初始化,而new申請時會呼叫建構函式對物件進行初始化

(3)malloc申請空間時需要傳遞申請空間的大小,new只需要跟上型別,由編譯器自動識別。

(4)malloc申請的空間需要強制型別轉化

(5)malloc申請空間失敗時,返回的是空指標,因此使用時必須判空,new不需要,但是new需要捕獲異常 

(6)malloc申請的空間一定在堆上,而new不一定,因為operator new可以由使用者自己實現。

(7)new/delete的效率稍低,因為new/delete是由malloc/free封裝而成的

七.面試題

1.建立一個類,該類只能在堆上申請空間

思路:在堆上建立一個類,我們可以把建構函式和拷貝建構函式私有化,然後建立一個靜態的函式來完成對物件的建立

class onlyheap
{
public:
	static onlyheap* CreateObject()
	{ 
		return new onlyheap;
	}
private:
	onlyheap(){};//建構函式
	onlyheap(const onlyheap&);
};

2.建立一個類,該類只能在棧上申請空間

只有使用了new操作符,物件才會建立在堆上,因此只要禁用new運算子就可以實現類物件只能建立在棧上,將operator new設為私有

class StackOnly    
{ 
public:        
	StackOnly()  {} 
private:        
	void* operator new(size_t size);    
	void operator delete(void* p);
};