C++----動態記憶體管理
一、動態記憶體開闢
1、C語言中c語言中malloc,calloc,realloc
(1)共同點:a、都是從堆上開闢記憶體空間的,必須釋放,否則會造成記憶體洩漏。
b、返回值:返回值都是"void *"
(2)不同點:a、malloc只有一個引數,引數為所需要申請空間位元組數的大小:malloc使用前一定要進行檢測,防止拿到沒用的空間。申請成功:返回申請的地址;失敗:返回空。calloc有兩個引數:第一個是元素個數,第二個是單個元素的大小;且需要初始化為0。申請成功,返回申請的首地址,失敗,返回空。c、realloc有兩個引數:第一個表示起始地址(void *p),第二個引數為size, 位元組數。既可以把空間擴大、也可以把空間減少。p為NULL,malloc;非空,size(變小:返回空間原地址;擴容:看size的大小,看是否需要開闢新空間。malloc申請空間,保證一塊空間只能被一個人使用,如何保證:把空間管理起來,在空間的前面加一個結構體,申請的空間的大小,遠大於輸入的數字。管理空間的大小 。有一個結構體,結尾還有四個位元組。可能會造成空間的浪費。拿到結構體的大小即知道這個空間有多大,縮小也是通過這個結構體。
模擬實現一個malloc:
2、在棧上申請一個空間:使用_alloca在棧上動態開闢記憶體,棧上開闢的記憶體由編譯器自動維護,不需要使用者顯式釋放.出了作用域系統自動釋放。
3、常見記憶體洩漏:
(1)記憶體申請之後忘記釋放
(2)程式邏輯不清,以為釋放了,實際記憶體洩漏【兩個指標指向同一塊空間,釋放兩次,會導致同一塊空間被釋放兩次,造成記憶體洩漏。】
(3)程式誤操作,將堆破壞
char *pTest3 = (char *)malloc(5);
strcpy(pTest3, "Memory Leaks!");
free(pTest3);
拷貝時會發生越界,越界後進行釋放,覆蓋了4個FD點,可以少用,但是不能多用(4)釋放時傳入的地址和申請時的地方不同
那麼什麼是記憶體洩漏,如何進行記憶體洩漏檢測呢?
二、c++記憶體管理
1、如何申請:
int main()
{
//申請單個型別的空間
int *p1 = new int;
int *p2 = new int(10);//申請一個整型空間,大小為10
//申請10個整型空間
int *p3 = new int[10];
return 0;
}
2、釋放
int main() { //申請單個型別的空間 int *p1 = new int; int *p2 = new int(10);//申請一個整型空間,大小為10 //申請10個整型空間 int *p3 = new int[10]; //釋放單個空間 delete p1; delete p2; //釋放連續空間 delete[] p3; return 0; }
new申請的空間不需要進行判空,但是malloc申請空間失敗後會返回一個空。new和delete只是c++中的兩個操作符,不是函式。而且這兩個操作符可以進行過載。
3、自定義型別:
class Test
{
public:
Test()
{
_t = 0;
cout << "Test():" << endl;
}
~Test()
{
cout << "~Test():" << endl;
}
private:
int _t;
};
int main()
{
Test *pt1 = (Test*)malloc(sizeof(Test));
//在c++中用malloc申請的空間不是物件,不會呼叫建構函式。malloc繼承自C語言,C語言中沒有建構函式
Test *pt2 = new Test;
//用new申請的空間,不僅申請了空間,還執行了建構函式。
//free(pt1);
delete pt2;
}
用delete釋放:
用free釋放
int main()
{
Test *pt1 = (Test*)malloc(sizeof(Test));
//在c++中用malloc申請的空間不是物件,不會呼叫建構函式。malloc繼承自C語言,C語言中沒有建構函式
Test *pt2 = new Test;
//用new申請的空間,不僅申請了空間,還執行了建構函式。
free(pt1);
//delete pt2;
system("pause");
}
說明malloc,free沒有呼叫建構函式;而new,delete呼叫了建構函式
int main()
{
Test *pt3 = new Test[10];
delete[] pt3;
}
delete釋放的是單個物件的空間,delete[]銷燬一段連續的空間。先申請的物件,最後銷燬。
3、需要匹配使用:
(1)
A、沒有呼叫建構函式,就去釋放資源,會導致程式崩潰,
class Test
{
public:
Test()
{
_t = 0;
cout << "Test():" << endl;
}
~Test()
{
cout << "~Test():" << endl;
}
private:
int _t;
};
int main()
{
Test *p1 = (Test*)malloc(sizeof(Test));
delete p1;
return 0;
}
執行結果中,只有解構函式的執行結果而沒有建構函式的執行結果。並且程式會發生記憶體洩漏,建構函式和解構函式需要成對使用。B、
int main()
{
Test *p2 = (Test*)malloc(sizeof(Test));
delete[] p2;
return 0;
}
此時釋放就立刻返回中斷
C、
int main()
{
Test *p3 = new Test;//可能發生記憶體洩漏
//Test *p4 = new Test;//程式會崩潰
free(p3);
//delete[] p4;
//Test *p5 = new Test[10];程式會發生崩潰
Test *p6 = new Test[10];//程式碼發生崩潰,解構函式不能列印
//free(p5);
delete p6;
system("pause");
return 0;
}
總結:mallo申請的空間,不用free去進行釋放,或者new申請的空間,不用delete釋放,有的情況會發生記憶體洩漏,有的情況下會發生程式崩潰;
a、此處會發生錯誤:
Test* pt=new Test[10];
cout<<(*(int *)pt-1)<<endl;
delete pt;
此處會發生錯誤是因為:delete一次釋放一個,不會把指標向前倒四個,所以一定會出現問題。
b、 如下程式碼一定會崩潰:
Test *pt=new Test;
delete[] pt;
因為要到函式的前四個位元組取解構函式的次數,是一個無意義的東西。
如果不寫解構函式,則採用上述程式碼,不會出現崩潰,不寫解構函式,不用進行析構。沒有多申請的4個位元組。沒有解構函式,則並不用呼叫解構函式,就不用多給4個位元組儲存物件的個數。,直接釋放。如果給出了解構函式,則必須成對使用;沒有給出解構函式,則不會因為不匹配而出現記憶體洩漏等問題。
4、因為建構函式是朝物件裡面放東西,所以先開闢空間,空間開闢好了再呼叫建構函式,朝空間裡面放東西。
C++中不支援垃圾回收。在底層有個_callnewh函式指標,是空間不足時的應對措施,但是這部分釋放的工作應該由使用者來完成,使用者自己才能知道空間裡面哪些空間不用使用。丟擲異常,知道new申請失敗。
class Test
{
public:
Test()
{
_t = 0;
cout << "Test():" << endl;
}
~Test()
{
cout << "~Test():" << endl;
}
private:
int _t;
};
int main()
{
Test *pt = new Test;
delete pt;
system("pause");
return 0;
}
new要做的事情:a、申請物件的空間:在底層呼叫operator new函式,用malloc連續申請,直到申請成功。如果用malloc申請失敗了:是否有空間不足的應對措施,,如果有,就繼續申請,直到申請成功,如果沒有,則丟擲異常。b呼叫建構函式。
銷燬時,delete要做的事情:清理資源後釋放空間,。a、先呼叫物件的解構函式,清空物件中的資源;b、呼叫operator delete(p)函式釋放空間:先檢測要釋放的空間是否為空,如果為空,則返回;如果不是空,就呼叫free。(new和delete實際上就是把malloc和free重新封裝了一次。)
new Test[10]要做的事情:(Test *pt=new Test[n])a、先呼叫operator new[](sizeof(Test)*n)函式;再呼叫operator new[](sizeof(Test)*n+4)函式;在operrator new()裡面通過malloc申請空間,同上;pt拿到的地址就是operator new()申請到的地址是向後偏轉4個位元組【pt是operator new[]返回值向後偏轉4個位元組的位置】【10為物件的個數,呼叫解構函式參考】【呼叫建構函式構造n個物件】;b、空間申請成功後,返回空間的首地址;c、在operator new()函式體裡面返回函式的值;d、執行建構函式。在底層申請的空間比真實空間多4個位元組,這4個位元組裡面若為整形【*((int *)pt-1)可以找到多出的四個位元組的位置。 】
delete[] pt要做的事情:a、清空物件中的資源(必須知道有多少空間(讓pt向前偏移4個位元組,取物件的個數即呼叫解構函式的次數),迴圈呼叫解構函式呼叫物件中涉及的資源);b、釋放空間【用operator new[]釋放資源(通過free釋放)】【歸還時,從起始空間的起始地址開始釋放】
(1)如果自己寫的operator new()函式,則先呼叫自己寫的,但是仍然會呼叫建構函式,因為此處的物件是建構函式。
class Test
{
public:
Test()
{
_t = 0;
cout << "Test():" << endl;
}
~Test()
{
cout << "~Test():" << endl;
}
private:
int _t;
};
void *operator new(size_t size)
{
return malloc(size);
}
int main()
{
Test *pt = new Test;
return 0;
}
(2)構造一個全域性的operator new()函式和一個區域性的,庫中還有一個,這三個函式是可以共存的。此處先呼叫類裡面的函式
class Test
{
public:
Test()
{
_t = 0;
cout << "Test():" << endl;
}
void *operator new(size_t size)
{
return malloc(size);
}
~Test()
{
cout << "~Test():" << endl;
}
private:
int _t;
};
void *operator new(size_t size)
{
return malloc(size);
}
int main()
{
Test *pt = new Test;
return 0;
}
申請空間是在哪一個檔案的哪一個函式體的哪一行申請的:
class Test
{
public:
Test()
{
_t = 0;
cout << "Test():" << endl;
}
~Test()
{
cout << "~Test():" << endl;
}
private:
int _t;
};
void *operator new(size_t size,const char *strFileName,const char* strFunc,size_t line)
{
cout << strFileName << ":" << strFunc << ":" << line << ":" << size << endl;
return malloc(size);
}
#ifdef DEBUG
#define new new(__FILE__, __FUNCDNAME__, __LINE__) Test
#endif
int main()
{
Test *pt = new Test;
return 0;
}
5、new底層由malloc建立,malloc申請的空間比實際需要的空間大,這些節點在用new建立時,還要開闢一個結構體的大小,會造成空間的浪費。
typedef int DataType;
struct Node
{
Node *_pNext;
DataType _data;
Node(const DataType& data)
:_pNext(NULL)
, _data(data)
{}
};
class List
{
public:
List()
:_pHead(NULL)
{}
void PushBack(const DataType& data)
{
if (NULL == _pHead)
_pHead = new Node(data);
else
{
Node *pTail = _pHead;
while (pTail->_pNext)
pTail = pTail->_pNext;
pTail->_pNext = new Node(data);
}
}
private:
Node *_pHead;
};
那麼,我們可以先把節點的大小通過malloc一次性給出來,此時只需要多餘的一個結構體,節省了空間。通過連結串列把節點連結起來。但是通過malloc開闢的空間,此時開闢的空間並不是節點,只是和節點相同大小的一段空間,需要呼叫建構函式。(在已經存在的空間上面執行建構函式)
int main()
{
Node *pt = (Node*)malloc(sizeof(Node));
new(pt + 3) Node(3);//地址加上偏移量,定位到第三個節點,執行其建構函式(定位New表示式)
int array[10];
array[0] = 0;
new(array + 3) int(3);
return 0;
}
一個連結串列每個節點都用new建立,會有很多空間的浪費,有一些額外的記憶體空間,此時可以通過malloc申請一塊大的記憶體空間出來,然後從這塊空間裡面,一個一個的拿空間出來用。malloc申請的空間不是一個物件,需要通過定位new表示式,生成一個物件。定位new表示式:“new(地址) 型別相應的構造”:在這塊地址上面執行建構函式,把空間變成一個類物件。