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