c++的動態內存分配

c雖然也提供了動態內存分配的相關函數,但與c++還是有很大的不同。這裏先介紹c++的內存分配,再與c的進行對比。

c++使用new來進行內存申請,new運算符是基於類型進行內存分配的,即程序員要告訴new,要爲哪種數據類型分配內存,數據類型可以是基本數據類型,也可以是自己定義的結構或者是類。返回一個指向已分配內存塊的指針,我們要做的就是把這個內存塊的地址賦給一個對應類型的指針。

typeName * pointerName = new typeName;

指針本身只標註了內存塊的起始位置,將他賦給對應類型的指針變量之後就能得到類型和長度信息,從而訪問內存上的數據。與變量在定義時從棧分配的內存不同,new分配的內存屬於堆或自由存儲區

在某些情況下,計算機的內存不足導致無法分配內存,無法滿足new的請求,這時new會返回一個異常(之後將會討論異常處理機制),目前僅需知道c++有檢測並處理內存分配失敗的工具。

當使用完內存時,需要將內存空間還給系統,使用delete來釋放用new申請的內存

注意:只有使用new分配的內存區域才能被delete釋放,普通的變量聲明分配的內存無法被delete釋放,另外,不要釋放已經釋放的內存地址,這樣的行爲是未定義的。因此最好不要創建指向同一地址的兩個不同指針以減小重複釋放的概率。

使用new創建動態數組

這裏的數組就不一定是基本類型數組,對於大型結構來說,使用new創建動態結構數組比較方便。對於數組內存有兩種方式,在編譯時爲數組分配內存即數組大小確定,稱爲靜態聯編;而對於在程序運行過程中分配數組內存的方式稱爲動態聯編。

int * pt = new int[10];

delete [] pt;

上面的例子就用new分配了一個長度是10的int數組,並且返回了首元素地址。程序中需要程序員自己去跟蹤數組元素的數目,並且保證在釋放內存時使用的指針指向內存塊的首地址。

delete []表示將整個數組釋放,事實上程序的確跟蹤了數組分配的內存量,以便在delete[]時能夠正確地釋放內存,但這個信息不是共享的,比如不能用sizeof確定動態數組的字節數。

仍需注意,new和delete必須一一對應使用,用new []分配的內存也必須使用delete[]釋放。

new的其他注意點

一般來說,編譯器將程序的內存劃分爲三塊:靜態區保存自動全局變量和static變量(包括局部static變量);棧區保存局部變量,在代碼塊執行完後即釋放;堆區保存動態存儲變量,由malloc系列函數或new操作符請求的變量,在手動釋放之前一直存在。

在瞭解了c++的內存模型後,在使用new時應注意的是,new本身分配的內存需要手動釋放,但若將其返回的內存塊地址賦給一個局部指針變量,這個局部指針變量在離開其作用之後就會被釋放,若沒有將內存塊釋放或用其他指針變量繼承該地址,會導致不可預見的內存泄漏。

new的更多特性

1、使用new運算符進行初始化:new分配空間之後可以對對應類型進行初始化,如:

int *pi = new int {6};//*pi set to 6
double *pd = new double {88.90};
...
struct where{double x; double y;double z};
where *pt1 = new where {2.33,6.66 2.13};//also can be used to iitilize the menber of struct or class or array

此外,當用new對類對象分配空間是會調用構造函數,同理,在用delete釋放時會調用類的析構函數,總結來說:

new做的事:

1.調用operator new分配空間

2.調用構造函數初始化空間

 

delete做的事:

1.調用析構函數清理對象

2.調用operator delete釋放空間

 

new[N]做的事:

1.調用operator new分配空間

2.調用N次構造函數分別初始化每個對象

 

delete做的事:

1.調用N次析構函數清理對象

2.調用operator delete釋放空間

2、new失敗時不會返回空指針,而是會引發異常std::bad_alloc

3、new系列運算符的內部定義:包括分配函數和釋放函數

void * operator new(std::size_t);//used by new
void * operator new[](std::size_t);//used by new[]
void * operator delete(void *);
void * operator delete[](void *);

在運行如下代碼時,實際上是經過轉換後調用對應的函數:

int * pi = new int;
//轉換後
int * pi = new(sizeof(int));

int *pt = new int[40];
//轉換後
int * pt = new(40*sizeof(int));

delete pt;
//轉換後
delete (pt);

在c++中,這些函數是可替換的,故可以對new和delete提供替換函數,以滿足特殊的內存分配方式。可定義作用域爲類的new函數,在對該類進行new操作的時候就會調用爲該類特別設計的函數。

4、定位new運算符

通常new是在堆中尋找一個滿足要求的內存塊,此外還有一個變體,可以指定要使用的內存位置,稱爲定位new運算符。可以利用這個特性設置專門的內存管理規程或者在處理需要使用特定地址訪問的硬件或是在特定位置創建對象。

這種特性需要包含頭文件new,其有四種用法

#include<new>
struct chaff
{
    char dross[20];
    int slag;
};
char buffer1[50];
char buffer2[500];
int main()
{
    chaff *pt1, *pt2;
    int *p3, *p4;
//regular form
    p1 = new chaff;
    p3 = new int[20];
//placement form
    p2 = new (buffer1) chaff;//place structure in buffer1
    p4 = new (buffer2) int[20];//place array in buffer 2

定位new運算符不會檢查當前位置是否被使用,但會檢查數組下標越界,即不允許分配超過預分配緩衝區大小的對象:

#include<bits/stdc++.h>
using namespace std;
int main()
{
    char buffer1[20];
    int * pt = new (buffer1) int[310] {1,2,3};
    cout << pt;
    system("pause");
    return 0;
}

以上代碼能通過編譯,但會拋出運行時錯誤。

定位new的內部原理:

定位new直接接受一個地址並在其上分配內存而不會跟蹤其是否已經被使用,也就是對同一個地址多次使用定位new就會覆蓋原有的數據。

其次其實現是對指針進行強制類型轉換,雖然地址相同,但緩衝區的指針是char*類型,new對其進行強制類型轉換爲void*再返回以便賦給任意一種類型的指針

由於定位new不會跟蹤內存的使用情況,因此不能使用delete釋放被定位new分配的內存塊,delete只適用於堆中的動態內存。如果緩衝區是用new分配的動態內存,則可以使用delete釋放整個緩衝區內存塊。

不能使用delete釋放也意味着只能通過手動地刪除被創建的對象,按照與創建時相反的順序刪除對象,以防止後面創建的對象依賴前一個對象導致的錯誤。另外,系統不會自動調用析構函數進行清理,因此需要顯式地調用析構函數來清理。最後再將整個緩衝區釋放。

對比new和malloc

1.它們都是動態管理內存的入口。

2.malloc/free是c/c++標準庫的函數,new/delete是c++操作符。

3.malloc/free只是動態分配/釋放內存空間。而new/delete出來分配空間還會調用構造函數和析構函數進行初始化與清理。

4.malloc/free需要手動計算類型大小且會返回void*, new/delete可以自己計算類型的大小,返回對應類型的指針。

關於c的動態內存分配詳見另一個筆記。事實上new、delet是對malloc、free的封裝

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章