內(nèi)存管理

內(nèi)存管理

C++使用new和delete兩個(gè)運(yùn)算符進(jìn)行內(nèi)存管理。

使用new進(jìn)行動(dòng)態(tài)分配和初始化對(duì)象

在自由空間內(nèi)分配的內(nèi)存是無(wú)名的,因此new無(wú)法為其分配的對(duì)象進(jìn)行命名,而是返回一個(gè)指向該對(duì)象的指針:

int *pi=new int;

上述表達(dá)式在自由空間(??臻g?堆空間?)內(nèi)構(gòu)造一個(gè) int對(duì)象,并且返回一個(gè)指向這個(gè)對(duì)象的指針。

默認(rèn)情況下,動(dòng)態(tài)分配的對(duì)象是默認(rèn)初始化的,這就意味著內(nèi)置類型(int)或者是組合類型的對(duì)象的值是未定義的,而類類型的對(duì)象將會(huì)使用默認(rèn)構(gòu)造函數(shù)進(jìn)行初始化:

string *ps=new string;
int *pi=new int;

舊標(biāo)準(zhǔn)下,可以使用直接初始化的方式進(jìn)行初始化一個(gè)動(dòng)態(tài)分配對(duì)象。也可以使用傳統(tǒng)的構(gòu)造方式(使用圓括號(hào))

int *pi=new int(1024);  //使用直接初始化的方式初始化一個(gè)動(dòng)態(tài)分配對(duì)象
string *ps=new string(10,'9');  //使用傳統(tǒng)的構(gòu)造方式進(jìn)行初始化的過(guò)程
vector<int> *pv=new vector<int>{0,1,2,3,4,5,6}; //使用列表初始化的形式

也可以對(duì)動(dòng)態(tài)分配的對(duì)象進(jìn)行值初始化,只需要在類型名之后添加括號(hào)即可

string *ps1=new string; //默認(rèn)初始化為空 string
string *ps=new string(); //值初始化為空string
int *pi1  = new int; //默認(rèn)初始化,*pi1的值未定義
int *pi2 = new int(); //值初始化為0. *pi2 為 0 這種方式只能用于初始化指針,不能用于初始化int對(duì)象

值初始化內(nèi)置類型對(duì)象具有良好定義的值,默認(rèn)初始化對(duì)象的值則是未定義的。對(duì)于類中那些依賴編譯器射程的默認(rèn)構(gòu)造函數(shù)的內(nèi)置類型成員,沒(méi)有進(jìn)行類內(nèi)初始化的時(shí)候,他們的值也是未定義的。

如果具有括號(hào)包圍的初始化器,可以使用auto來(lái)推斷我們想要分配的對(duì)象的類型。(需要保證使用單一初始化器)

auto p1=new auto(obj); //p指向一個(gè)和obj類型相同的對(duì)象,該對(duì)象使用obj進(jìn)行初始化。
auto p2=new auto{a,b,c};//括號(hào)中只能有單個(gè)初始化器

動(dòng)態(tài)分配的const對(duì)象

使用new 分配const對(duì)象是合法的

//分配并初始化一個(gè)const int
const int *pci=new const int(1024);
//分配并初始化一個(gè)const的空string
const string *pcs = new const string();

類似其他的任何const對(duì)象,一個(gè)動(dòng)態(tài)分配的const的對(duì)象必須進(jìn)行初始化。對(duì)于一個(gè)定義了默認(rèn)構(gòu)造函數(shù)的類類型,其const動(dòng)態(tài)對(duì)象可以陰式初始化。由于分配的對(duì)象是const的,new返回的指針是指向一個(gè)const的指針。

內(nèi)存耗盡

當(dāng)一個(gè)程序用光了所有的可用內(nèi)存之后,new表達(dá)式將會(huì)失敗,默認(rèn)情況下,當(dāng)new不能分配所要求的內(nèi)存空間的時(shí)候,將會(huì)拋出一個(gè)類型為bad_alloc的異常,可以通過(guò)改變new的使用方式阻止其拋出異常:

int *pi=new int();//如果分配失敗,new將會(huì)拋出std::bad_alloc
int *p2=new (nothrow) int();//如果分配失敗,new返回一個(gè)空指針。

我們將這種形式的new稱為定位new。定位new 表達(dá)式允許我們想new傳遞額外的參數(shù)。在上述例子中,我們傳遞給它一個(gè)標(biāo)準(zhǔn)庫(kù)定義的名為nothrow的對(duì)象。

釋放動(dòng)態(tài)內(nèi)存

為了防止內(nèi)存耗盡,在動(dòng)態(tài)內(nèi)存使用完畢之后,必須要將其歸還給系統(tǒng),我們通過(guò)使用delete表達(dá)式,將動(dòng)態(tài)內(nèi)存歸還給系統(tǒng)。delete表達(dá)式接收一個(gè)指針,指向我們想要釋放的對(duì)象。

delete p;//p需要指向一個(gè)動(dòng)態(tài)分配對(duì)象或者是一個(gè)空指針

delete表達(dá)式執(zhí)行兩個(gè)操作:銷(xiāo)毀給定指針指向的對(duì)象;釋放對(duì)應(yīng)的內(nèi)存。

指針值和delete

傳遞給delete的指針必須指向動(dòng)態(tài)分配的內(nèi)存,或者是一個(gè)空指針。釋放一塊并非new分配的內(nèi)存,或者是將相同的指針釋放多次,其行為是未定義的:

int i,*pi1 = &i,*pi2 = nullptr;
double *pd = new double(33), *pd2 = pd;
delete i;//錯(cuò)誤:i并非一個(gè)指針
delete pi1;//未定義:pi1指向一個(gè)局部變量
delete pd;//正確
delete pd2;//未定義: pd2指向的內(nèi)存已經(jīng)被釋放了
delete pi2;//正確: 釋放一個(gè)空指針總是沒(méi)有問(wèn)題的

對(duì)于delete pi1和pd2所產(chǎn)生的錯(cuò)誤是具有潛在危害的:通常情況下,編譯器不能分辨一個(gè)指針指向靜態(tài)還是動(dòng)態(tài)分配的對(duì)象。
雖然一個(gè)const對(duì)象的值不能被改變,但是是可以被銷(xiāo)毀的。

動(dòng)態(tài)對(duì)象的生存期直到被釋放為止

調(diào)用者使用返回指向動(dòng)態(tài)內(nèi)存的指針的函數(shù)的時(shí)候,需要進(jìn)行手動(dòng)的內(nèi)存釋放。

//factory 返回一個(gè)指針,指向一個(gè)動(dòng)態(tài)分配的對(duì)象
Foo* factory(T arg){
  return new Foo(arg);//調(diào)用者負(fù)責(zé)釋放這個(gè)內(nèi)存
}

調(diào)用者需要避免使用了但是未釋放的情況,如下所示:

void use_factory(T arg){
  Foo *p=factory(arg);//使用了p 但是沒(méi)有進(jìn)行delete
}//p離開(kāi)了它的作用域,但是它所指向的內(nèi)存并沒(méi)有被釋放。

內(nèi)置類型的對(duì)象被銷(xiāo)毀的時(shí)候什么也不會(huì)發(fā)生,特別是,當(dāng)一個(gè)指針離開(kāi)其作用域的時(shí)候,它所指向的對(duì)象什么也不會(huì)發(fā)生。如果這個(gè)指針指向的是動(dòng)態(tài)內(nèi)存,內(nèi)存將不會(huì)被自動(dòng)釋放。

使用new和delete進(jìn)行動(dòng)態(tài)內(nèi)存管理時(shí)常見(jiàn)的錯(cuò)誤

  • 沒(méi)有使用delete進(jìn)行內(nèi)存釋放,導(dǎo)致發(fā)生內(nèi)存泄漏
  • 使用已經(jīng)釋放掉的對(duì)象,通過(guò)釋放內(nèi)存之后將指針置為空
  • 同一塊內(nèi)存釋放兩次。當(dāng)有兩個(gè)指針指向相同的動(dòng)態(tài)分配對(duì)象時(shí)候,有可能會(huì)對(duì)其中一個(gè)指針進(jìn)行了delete操作,隨即又對(duì)第二個(gè)指針進(jìn)行了delete操作,導(dǎo)致自由空間被破壞。
    堅(jiān)持只使用只能指針,可以避免這些所有的問(wèn)題,對(duì)于一個(gè)內(nèi)存,只有在沒(méi)有任何智能指針指向他的時(shí)候,才會(huì)自動(dòng)進(jìn)行釋放。

delete之后重置指針值

當(dāng)我們delete一個(gè)指針之后,指針值就變成了無(wú)效。雖然指針已經(jīng)無(wú)效,但是指針仍然保存(已經(jīng)釋放了的)動(dòng)態(tài)內(nèi)存的地址。意味著這時(shí)候指針變成了空懸指針(指向一塊曾經(jīng)保存數(shù)據(jù)對(duì)象但是已經(jīng)無(wú)效的內(nèi)存的指針)。
未初始化指針的所有缺點(diǎn),空懸指針也有。為了解決這個(gè)問(wèn)題,可以在delete之后,將nullptr賦予指針,清楚的指向指針不指向任何對(duì)象。
動(dòng)態(tài)指針的問(wèn)題在于可能有多個(gè)指針指向了同一個(gè)內(nèi)存。delete內(nèi)存之后重置指針的方式只對(duì)當(dāng)前指針有效,對(duì)于仍然指向(已經(jīng)釋放掉)內(nèi)存的指針是沒(méi)有任何作用的。
同時(shí)在實(shí)際系統(tǒng)中,查找指向相同內(nèi)存的所有指針是異常困難的。

智能指針

c++標(biāo)準(zhǔn)庫(kù)提供了兩種智能指針來(lái)管理內(nèi)存對(duì)象。智能指針的功能類似于常規(guī)指針,但是只能指針可以自動(dòng)釋放所指向的對(duì)象。

shared_ptr允許多個(gè)指針指向同一個(gè)對(duì)象;unique_ptr則是獨(dú)占所指向的對(duì)象。

shared_ptr

只能指針是一種模板,當(dāng)創(chuàng)建了一個(gè)智能指針的時(shí)候,必須提供額外的信息-指針?biāo)赶虻念愋汀?/p>

shared_ptr<string> p1; //shared_ptr 可以指向string
shared_ptr<list<int>> p2; //shared_ptr 可以指向int的list

默認(rèn)初始化的智能指針中保存一個(gè)空指針。智能指針的使用方式和普通指針是類似的,通過(guò)解引用一個(gè)智能指針?lè)祷厮赶虻膶?duì)象。也可以用作判斷條件表明當(dāng)前智能指針是否為空。

//如果p1不為空,檢查它是否指向一個(gè)空string
if(p1 && p1->empty())
    *p1 = "hi"; //如果p1指向一個(gè)空string ,解引用p1,將一個(gè)新值賦予string

shared_ptr和uniqu_ptr都具有的操作如下:

shared_ptr<T> sp 空智能指針,指向類型為T(mén)的對(duì)象
unqiue_ptr<T> up 空智能指針,指向類型為T(mén)的對(duì)象
p 將p作為一個(gè)條件判斷,如果p指向一個(gè)對(duì)象,則為true
*p 解引用p,獲取其指向的對(duì)象
p->mem 等價(jià)于(*p).mem
p.get() 返回p中保存的指針
swap(p,q) 交換p,q保存的指針
p.swap(q) 交換p,q保存的指針

shared_ptr所獨(dú)有的操作

make_shared<T>(args) 返回一個(gè)shared_ptr,指向一個(gè)動(dòng)態(tài)分配類型的T的對(duì)象,使用args初始化對(duì)象
shared_ptr<T> p(q) p是shared_ptr q的拷貝,這個(gè)操作會(huì)遞增q中的計(jì)數(shù)器,q中的指針必須可以轉(zhuǎn)化為T(mén)*
p=q p和q都是shared_ptr,所保存的指針必須可以相互轉(zhuǎn)換。此操作會(huì)遞減p的引用計(jì)數(shù),遞增q的引用計(jì)數(shù);若p的引用計(jì)數(shù)變?yōu)?,將其管理的內(nèi)存進(jìn)行釋放
p.unique() 如果p.use_count()為1,返回true,否則返回false;
p.use_count() 返回和p共享對(duì)象的智能指針的數(shù)量;主要用于調(diào)試

make_shared函數(shù)

最安全的分配和使用動(dòng)態(tài)內(nèi)存的方法是調(diào)用一個(gè)名為make_shard的標(biāo)準(zhǔn)庫(kù)函數(shù)。這個(gè)函數(shù)在動(dòng)態(tài)內(nèi)存中分配一個(gè)對(duì)象并進(jìn)行初始化。返回指向這個(gè)對(duì)象的shared_ptr。
使用make_shared函數(shù)的時(shí)候,必須制定想要?jiǎng)?chuàng)建的對(duì)象的類型。定義的方式和模板類相同。

//指向一個(gè)值為42的int的shared_ptr
shared_ptr<int> p3= make_shared<int>(42)
//p4指向一個(gè)值為“999999999”的string
shared_ptr<string> p4 = make_shared<string>(10,'9');
//p5指向一個(gè)值初始化的int,即值為0
shared_ptr<int> p5 =make_shared<int>();

shared_ptr的拷貝和賦值

當(dāng)進(jìn)行拷貝和賦值操作的時(shí)候,每個(gè)shared_ptr都會(huì)記錄有多少個(gè)其他shared_ptr和其指向的是相同的對(duì)象:
我們可以認(rèn)為每個(gè)shared_ptr都是具有一個(gè)關(guān)聯(lián)計(jì)數(shù)器的,稱其為引用計(jì)數(shù)。無(wú)論何時(shí)我們拷貝一個(gè)shared_ptr,引用計(jì)數(shù)的值都會(huì)遞增。例如:

  • 使用一個(gè)shared_ptr初始化另一個(gè)shared_ptr;
  • 將shared_ptr作為一個(gè)參數(shù)傳遞給一個(gè)函數(shù);
  • 作為函數(shù)的返回值;
    當(dāng)我們給一個(gè)shared_ptr賦予一個(gè)新值或者是shared_ptr被銷(xiāo)毀 (一個(gè)局部的shared_ptr)離開(kāi)其作用域的時(shí)候,計(jì)數(shù)器就會(huì)進(jìn)行遞減。
    一旦一個(gè)shared_ptr的計(jì)數(shù)器變?yōu)?,就會(huì)自動(dòng)釋放所管理的對(duì)象。

shared_ptr自動(dòng)銷(xiāo)毀所管理的對(duì)象

當(dāng)指向一個(gè)對(duì)象的最后一個(gè)shared_ptr被銷(xiāo)毀的時(shí)候,shared_ptr將會(huì)自動(dòng)銷(xiāo)毀這個(gè)對(duì)象。這是通過(guò)特殊的成員函數(shù)析構(gòu)函數(shù)完成銷(xiāo)毀的動(dòng)作。

shared_ptr自動(dòng)釋放相關(guān)聯(lián)的內(nèi)存

當(dāng)動(dòng)態(tài)對(duì)象不再被使用的時(shí)候,shared_ptr類將會(huì)自動(dòng)釋放動(dòng)態(tài)對(duì)象,這一特性使得動(dòng)態(tài)內(nèi)存的使用變得較為簡(jiǎn)單。

//factory返回一個(gè)shared_ptr,指向一個(gè)動(dòng)態(tài)分配的對(duì)象
shared_ptr<Foo> factory(T arg){
  return make_shared<Foo>(arg);
}

由于factory返回一個(gè)shared_ptr,所以我們確保它分配的對(duì)象在恰當(dāng)?shù)臅r(shí)候被釋放。
由于在最后一個(gè)shared_ptr銷(xiāo)毀前內(nèi)存都不會(huì)釋放,保證shared_ptr在無(wú)用之后不再保留是非常重要的。
shared_ptr在無(wú)用之后的一保留的一種可能的情況是,將shared_ptr存放在了一個(gè)容器之中,隨后排了容器,從而不再需要某些元素,在這種情況下應(yīng)該確保使用erase刪除不再需要的shared_ptr元素

不要使用get初始化另一個(gè)智能指針或者是為智能指針賦值

智能指針類型定義了一個(gè)名為get的函數(shù),返回一個(gè)內(nèi)置指針,指向智能指針管理的對(duì)象。這種情況是為了這樣的一種情況設(shè)計(jì):我們需要向不能使用智能指針的代碼傳遞一個(gè)內(nèi)置指針。使用get返回指針的代碼不能delete這個(gè)指針。

使用了動(dòng)態(tài)生存期資源的類

程序使用動(dòng)態(tài)內(nèi)存主要出于以下的原因:

  1. 程序不知道自己需要使用多少對(duì)象
  2. 程序不知道所需對(duì)象的準(zhǔn)確類型
  3. 程序需要在多個(gè)對(duì)象之間共享數(shù)據(jù)。

智能指針和異常

如果使用智能指針,即使程序塊過(guò)早結(jié)束,智能指針類也能確保內(nèi)存在不再需要的時(shí)候?qū)⑵溽尫牛?/p>

void f(){
  shared_ptr<int> sp(new int(42));//分配一個(gè)新對(duì)象
  //這段代碼拋出一個(gè)異常,且在f中沒(méi)有被捕獲
}//在函數(shù)結(jié)束的時(shí)候,shared_ptr將會(huì)自動(dòng)釋放內(nèi)存

函數(shù)對(duì)象的退出有兩種可能,正常處理結(jié)束或者是發(fā)生了異常,無(wú)論哪種情況,局部對(duì)像都會(huì)被銷(xiāo)毀,而在局部對(duì)象銷(xiāo)毀的過(guò)程中將會(huì)檢查引用計(jì)數(shù),就會(huì)保證了智能指針在異常情況下,可以釋放掉內(nèi)存。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 內(nèi)存管理 簡(jiǎn)述OC中內(nèi)存管理機(jī)制。與retain配對(duì)使用的方法是dealloc還是release,為什么?需要與a...
    丶逐漸閱讀 2,080評(píng)論 1 16
  • 11.看下面的程序,第一個(gè)NSLog會(huì)輸出什么?這時(shí)str的retainCount是多少?第二個(gè)和第三個(gè)呢? 為什...
    AlanGe閱讀 833評(píng)論 1 4
  • 內(nèi)存是計(jì)算機(jī)非常關(guān)鍵的部件之一,是暫時(shí)存儲(chǔ)程序以及數(shù)據(jù)的空間,CPU只有有限的寄存器可以用于 存儲(chǔ)計(jì)算數(shù)據(jù),而大部...
    dreamer_lk閱讀 1,609評(píng)論 2 10
  • 12.1 智能指針 智能指針行為類似普通指針,但它負(fù)責(zé)自動(dòng)釋放所知的對(duì)象。 #include <memory> s...
    龍遁流閱讀 427評(píng)論 0 1
  • 如果現(xiàn)在流的淚 是當(dāng)初腦子進(jìn)的水,那么,此刻淚流干了,是不是也該清醒了。
    緗怡閱讀 222評(píng)論 0 0

友情鏈接更多精彩內(nèi)容