您好,登錄后才能下訂單哦!
這篇文章主要介紹了C++中動態(tài)內(nèi)存分配的示例分析,具有一定借鑒價值,感興趣的朋友可以參考下,希望大家閱讀完這篇文章之后大有收獲,下面讓小編帶著大家一起了解一下。
C++動態(tài)內(nèi)存分配
為了解決這個普通的編程問題,在運行時能創(chuàng)建和銷毀對象是基本的要求。當然,C已提供了動態(tài)內(nèi)存分配函數(shù)malloc( )和free( ),以及malloc( )的變種(realloc:改變分配內(nèi)存的大小,calloc:指針指向內(nèi)存前初始化),這些函數(shù)在運行時從堆中(也稱自由內(nèi)存)分配存儲單元,但是運用這些庫函數(shù)需要計算需要開辟內(nèi)存的大小,容易出現(xiàn)錯誤。
那么通常我們在C語言中我們開辟內(nèi)存的方式如下:
(void*)malloc(sizeof(void));
然而,在C+ +中這些函數(shù)不能很好地運行。構(gòu)造函數(shù)不允許通過向?qū)ο髠鬟f內(nèi)存地址來初始化它。如果那么做了,我們可能
忘記了。則對象初始化在C + +中難以保證。
期望某事發(fā)生,但結(jié)果是在給對象初始化之前意外地對對象作了某種改變。
把錯誤規(guī)模的對象傳遞給了它。
當然,即使我們把每件事都做得很正確,修改我們的程序的人也容易犯同樣的錯誤。不正確的初始化是編程出錯的主要原因,所以在堆上創(chuàng)建對象時,確保構(gòu)造函數(shù)調(diào)用是特別重要的。
C+ +是如何保證正確的初始化和清理并允許我們在堆上動態(tài)創(chuàng)建對象的呢?
答案是使動態(tài)對象創(chuàng)建成為語言的核心。malloc( )和free( )是庫函數(shù),因此不在編譯器控制范圍之內(nèi)。如果我們有一個能完成動態(tài)內(nèi)存分配及初始化工作的運算符和另一個能完成清理及釋放內(nèi)存工作的運算符,編譯器就可以保證所有對象的構(gòu)造函數(shù)和析構(gòu)函數(shù)都會被調(diào)用。
若使用原始的動態(tài)內(nèi)存開辟方式就會顯得很繁瑣,具體代碼如下:
#include<cstdlib> #include<cstring> #include<iostream> using namespace std; class Obj { int i,j,k; enum {sz=100}; char buf[sz]; public: void initialize() { cout<<"initialize"<<endl; i=k=j=0; memset(buf,0,sz); } void destroy() const { cout<<"destroying Obj"<<endl; } }; int main() { Obj* obj=(Obj*)malloc(sizeof(Obj)); if(obj!=0) obj->initialize(); obj->destroy(); free(obj); return 0; }
在上面這行代碼中,我們可以看到使用malloc( )為對象分配內(nèi)存:obj* Obj = (obj*)malloc(sizeof(obj)) ;
這里用戶必須決定對象的長度(這也是程序出錯原因之一)。因為它是一塊內(nèi)存而不是一個對象,所以malloc( )返回一個void*.C++不允許將一個void* 賦予任何指針,所以必須映射。因為malloc( )可能找不到可分配的內(nèi)存(在這種情況下它返回 0),所以必須檢查返回的指針以確信內(nèi)存分配成功。
但最壞的是:Obj->initialize( ) ;用戶在使用對象之前必須記住對它初始化。注意構(gòu)造函數(shù)沒有被使用,因為構(gòu)造函數(shù)不能被顯式地調(diào)用—而是當對象創(chuàng)建時由編譯器調(diào)用。這里的問題是現(xiàn)在用戶可能在使用對象時忘記執(zhí)行初始化,因此這也是引入程序缺陷的主要來源。許多程序設(shè)計者發(fā)現(xiàn) C的動態(tài)內(nèi)存分配函數(shù)太復雜,令人混淆。所以, C程序設(shè)計者常常在靜態(tài)內(nèi)存區(qū)域使用虛擬內(nèi)存機制分配很大的變量數(shù)組以避免使用動態(tài)內(nèi)存分配。因為C++能讓一般的程序員安全使用庫函數(shù)而不費力,所以應(yīng)當避免使用 C的動態(tài)內(nèi)存方法。C++中的解決方案是把創(chuàng)建一個對象所需的所有動作都結(jié)合在一個稱為new的運算符里。當用new(new的表達式)創(chuàng)建一個對象時,它就在堆里為對象分配內(nèi)存并為這塊內(nèi)存調(diào)用構(gòu)造函數(shù)。
因此,如果我們寫出下面的表達式foo *fp = new foo(1,2) ; 在運行時等價于調(diào)用malloc(sizeof(foo)),并使用(1,2)作為參數(shù)表來為
foo調(diào)用構(gòu)造函數(shù),返回值作為this指針的結(jié)果地址。在該指針被賦給 fp之前,它是不定的、未初始化的對象— 在這之前我們甚至不能觸及它。它自動地被賦予正確的 foo類型,所以不必進行映射。缺省的new還檢查以確信在傳遞地址給構(gòu)造函數(shù)之前內(nèi)存分配是成功的,所以我們不必顯式地確定調(diào)用是否成功。在本章后面,我們將會發(fā)現(xiàn),如果沒有可供分配的內(nèi)存會發(fā)生什么事情。我們可以為類使用任何可用的構(gòu)造函數(shù)而寫一個 ne w表達式。如果構(gòu)造函數(shù)沒有參數(shù),可以寫沒有構(gòu)造函數(shù)參數(shù)表的new表達式:
foo *fp = new foo ;我們已經(jīng)注意到了,在堆里創(chuàng)建對象的過程變得簡單了—只是一個簡單的表達式 ,它帶有內(nèi)置的長度計算、類型轉(zhuǎn)換和安全檢查。這樣在堆里創(chuàng)建一個對象和在棧里創(chuàng)建一個對象一樣容易。
new表達式的反面是delete表達式。delete表達式首先調(diào)用析構(gòu)函數(shù),然后釋放內(nèi)存(經(jīng)常是調(diào)用free( ))。正如new表達式返回一個指向?qū)ο蟮闹羔樢粯?delete表達式需要一個對象的地址。delete fp ;上面的表達式清除了早先創(chuàng)建的動態(tài)分配的對象foo。delete只用于刪除由new創(chuàng)建的對象。如果用malloc( )(或calloc( )或realloc( ))創(chuàng)建一個對象,然后用delete刪除它,這個行為是未定義的。因為大多數(shù)缺省的new和delete實現(xiàn)機制都使
用了malloc( )和free( ),所以我們很可能會沒有調(diào)用析構(gòu)函數(shù)就釋放了內(nèi)存。如果正在刪除的對象指針是 0,將不發(fā)生任何事情。為此,建議在刪除指針后立即把指針賦值為0以免對它刪除兩次。對一個對象刪除兩次一定不是一件好事,這會引起問題。
當創(chuàng)建一個new表達式時有兩件事發(fā)生。首先,使用運算符new分配內(nèi)存,然后調(diào)用構(gòu)造函數(shù)。在delete表達式里,調(diào)用析構(gòu)函數(shù),然后使用運算符 delete釋放內(nèi)存。我們永遠無法控制構(gòu)造函數(shù)和析構(gòu)函數(shù)的調(diào)用(否則我們可能意外地攪亂它們),但可以改變內(nèi)存分配函數(shù)運算 符new和delete。被new和delete使用的內(nèi)存分配系統(tǒng)是為通用目的而設(shè)計的。但在特殊的情形下,它不能滿足我們的需要。改變分配系統(tǒng)的原因是考慮效率:我們也許要創(chuàng)建和銷毀一個特定的類的非常多的對象以至于這個運算變成了速度的瓶頸。 C++允許重載new和delete來實現(xiàn)我們自己的存儲分配方案,所以可以像這樣處理問題。
另外一個問題是堆碎片:分配不同大小的內(nèi)存可能造成在堆上產(chǎn)生很多碎片,以至于很快用完內(nèi)存。也就是內(nèi)存可能還有,但由于是碎片,找不到足夠大的內(nèi)存滿足我們的需要。通過為特定類創(chuàng)建我們自己的內(nèi)存分配器,可以確保這種情況不會發(fā)生。
在嵌入和實時系統(tǒng)里,程序可能必須在有限的資源情況下運行很長時間。這樣的系統(tǒng)也可能要求分配內(nèi)存花費相同的時間且不允許出現(xiàn)堆內(nèi)存耗盡或出現(xiàn)很多碎片的情況。由客戶定制的內(nèi)存分配器是一種解決辦法,否則程序設(shè)計者在這種情況下要避免使用new和delete,從而失去了C + +很有價值的優(yōu)點。
當重載運算符new和delete時,記住只改變原有的內(nèi)存分配方法是很重要的。編譯器將用new代替缺省的版本去分配內(nèi)存,然后為那個內(nèi)存調(diào)用構(gòu)造函數(shù)。所以,雖然編譯器遇到new 時會分配內(nèi)存并調(diào)用構(gòu)造函數(shù),但當我們重載new時,可以改變的只是內(nèi)存分配部分。(delete 也有相似的限制。)
當重載運算符new時,也可以替換它用完內(nèi)存時的行為,所以必須在運算符new里決定做什么:返回0、寫一個調(diào)用new - handler的循環(huán)、再試著分配或用一個 bad_alloc異常處理重載new和delete與重載任何其他運算符一樣。但可以選擇重載全局內(nèi)存分配函數(shù),或為特定的類使用特定的分配函數(shù)
當全局版本的new和delete不能滿足整個系統(tǒng)時,對其重載是很極端的方法。如果重載全局版本,那么缺省版本就完全不能被訪問—甚至在這個重載定義里也不能調(diào)用它們。
重載的ne w 必須有一個size_t 參數(shù)。這個參數(shù)由編譯器產(chǎn)生并傳遞給我們,它是要分配內(nèi)存的對象的長度。必須返回一個指向等于這個長度(或大于這個長度,如果我們有這樣做的原因)的對象的指針,或如果沒有找到存儲單元(在這種情況下,構(gòu)造函數(shù)不被調(diào)用),返回一個0。然而如果找不到存儲單元,不能僅僅返回0,還應(yīng)該調(diào)用new-handler或進行異常處理,通知這里存在問題。
運算符new的返回值是一個void *,而不是指向任何特定類型的指針。它所做的是分配內(nèi)存,而不是完成一個對象的建立—直到構(gòu)造函數(shù)調(diào)用了才完成對象的創(chuàng)建,這是由編譯器所確保的動作,不在我們的控制范圍內(nèi)。
運算符delete接受一個指向由運算符new分配的內(nèi)存的void *。它是一個void *因為它是在調(diào)用析構(gòu)函數(shù)后得到的指針。析構(gòu)函數(shù)從存儲單元里移去對象。運算符 delete的返回類型是void。
下面提供了一個如何重載全局new和delete的簡單的例子:
#include <stdlib.h> void * operator new(size_t sz) { printf("operator new:%d bytes\n",sz); void* m=malloc(sz); if(!m) puts("out of memory"); return 0; } void operator delete(void* m) { puts("operator delete"); free(m); } class s { int i[100]; public: s(){puts("s::s()");} ~s(){puts("s::~s()");} }; int main() { puts("creating & destorying an int "); int* p=new int(47); delete p; puts("creating & destorying an s"); s* S=new s; delete S; puts("creating & destorying an s[3]"); s* SA=new s[3]; delete [] SA; }
這里可以看到重載new和delete的一般形式。為了實現(xiàn)內(nèi)存分配器,使用了標準 C庫函數(shù) malloc( )和free( )(可能缺省的new和delete也使用這些函數(shù))。它們還打印出了它們正在做什么的信息。注意,這里使用 printf( )和puts( )而不是i o s t r e a m s。當創(chuàng)建了一個i o s t r e a m對象時(像全局的c i n、c o u t和c e r r),它們調(diào)用new去分配內(nèi)存。用printf( )不會進入死鎖狀態(tài),因為它不調(diào)用new來初始化本身。
在main( )里,創(chuàng)建內(nèi)部數(shù)據(jù)類型的對象以證明在這種情況下重載的new和delete也被調(diào)用。然后創(chuàng)建一個類型s的單個對象,接著創(chuàng)建一個數(shù)組。對于數(shù)組,我們可以看到需要額外的內(nèi)存用于存放數(shù)組對象數(shù)量的信息。在所有情況里,都是使用全局重載版本的new和delete。
為一個類重載new和delete時,不必明說是 static,我們?nèi)允窃趧?chuàng)建 static成員函數(shù)。它的語法也和重載任何其他運算符一樣。當編譯器看到使用new創(chuàng)建類對象時,它選擇成員版本運算符new而不是全局版本的new。但全局版本的new和delete為所有其他類型對象使用(除非它們有自己的new和delete)。
如果為一個類重載了運算符new和delete,那么無論何時創(chuàng)建這個類的一個對象都將調(diào)用這些運算符。但如果為這些對象創(chuàng)建一個數(shù)組時,將調(diào)用全局運算符new( )立即為這個數(shù)組分配足夠的內(nèi)存。全局運算符 delete( )被調(diào)用來釋放這塊內(nèi)存。可以通過為那個類重載數(shù)組版本的運算符new [ ]和delete [ ]來控制對象數(shù)組的內(nèi)存分配。這里提供了一個顯示兩個不同版本被調(diào)用的例子:?這里,全局版本的new和delete被調(diào)用,除了加入了跟蹤信息以外,它們和未重載版本new 和delete的效果是一樣的。當然,我們可以在重載的new和delete里使用想要的內(nèi)存分配方案。
可以看到除了加了一個括號外,數(shù)組版本的new和delete與單個對象版本是一樣的。在這兩種情況下,要傳遞分配的對象內(nèi)存大小。傳遞給數(shù)組版本的內(nèi)存大小是整個數(shù)組的大小。應(yīng)該記住重載運算符new唯一需要做的是返回指向一個足夠大的內(nèi)存的指針。雖然我們可以初始化那塊內(nèi)存,但通常這是構(gòu)造函數(shù)的工作,構(gòu)造函數(shù)將被編譯器自動調(diào)用。
感謝你能夠認真閱讀完這篇文章,希望小編分享的“C++中動態(tài)內(nèi)存分配的示例分析”這篇文章對大家有幫助,同時也希望大家多多支持億速云,關(guān)注億速云行業(yè)資訊頻道,更多相關(guān)知識等著你來學習!
免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。