c 徹底消滅 記憶體洩漏 野指標 下篇

2021-10-05 16:40:27 字數 4452 閱讀 3718

6. 後記

前篇最後,我們為消除記憶體洩漏、野指標等問題所做的**嘗試還是存在問題,本篇我們來討論一下進一步的改進。

使用者只new開闢,但是忘記使用delete釋放,程式需要定期自動清理記憶體。

在之前的記憶體管理中,部分指標需要用delete,部分指標不能用delete,這導致使用者的困惑。後續改進中,需要支援對所有指標使用delete,程式自動判斷被delete的指標之前是否觸發過引用計數,若沒觸發,則不減少引用計數。

目前,現有的記憶體管理方案都是如下圖所示:

多個指標指向同乙個記憶體,他們都知道自己指向誰,但是堆記憶體卻不知道誰指向自己,因此,在自己被釋放的時候無法進行控制。

針對上述需求1,我們需要讓堆記憶體知道誰指向自己,定期查詢其是否還指向自己,如果不指向自己,則減少引用。

針對上述需求2,我們在拷貝指標的時候需要向堆記憶體註冊該指標,讓堆記憶體記住該指標,在記憶體釋放的時候經過註冊的指標才有資格釋放。這樣可以避免記憶體被沒有許可權的指標釋放。

要做的事情已經清晰了,但是,這裡還有乙個問題,operator new和operator delete的各種形式本身並不具備反向位址傳遞,也就是說,operator new和operator delete天生無法知道到底是哪個指標指向對記憶體。因此,若要解決上述問題,記憶體開闢和釋放就不能使用new和delete了。

我們先看**:

#include

#include

#include

#include

#define heap_sign_str ("heapyes")

///《堆記憶體標誌

#define num_byte_heap_sign 8

///《堆記憶體標誌大小,為heap_sign_str字串長度+1

static std::mutex memuselock;

class

memorymanager

public

:static memorymanager&

getinstance()

//提交指標至記憶體管理系統

template

<

typename t>

void

commit

(t*& p)

} m_ptr.

push_back((

void*)

(&p));

}//從記憶體管理系統移除指標

template

<

typename t>

void

remove

(t*& p)

else

}return;}

private

: std::vector<

void

*> m_ptr;};

template

<

typename t,

typename..

.args>

void

new(t*

& p, args&&..

. args)

template

<

typename t>

void

delete

(t*& p)

void

*operator

new(size_t sz)

}//將堆記憶體標誌拷貝到頭部

memcpy

(p, heap_sign_str, num_byte_heap_sign)

; p =

(char

*)p + num_byte_heap_sign;

//初始化引用計數為1*(

(int

*)p)=1

;//將指標指向物件資料區,並返回該指標,後續物件在該資料區構造儲存

p =(char

*)p +

sizeof

(int);

return p;

}void

operator

delete

(void

* p)

std::lock_guard<:mutex>

lock

(memuselock)

;//加鎖防止重複釋放記憶體if(

--(*pcount)==0

)//!=0時,要麼是已經釋放過了;要麼是還有引用,不應該釋放;

//兩種情況都應該直接返回。

return;}

//增加引用計數,指標複製時自動呼叫,對使用者透明

inline

void

add_ref

(void

* p)++(

*pcount);}

//指標複製方法1:函式法。

template

<

typename tx,

typename ty>

inline

void

ptr_copy

(tx*

& pdst, ty* psrc)

//指標複製方法2:過載操作符

class

ptrbase

ptrbase()

ptrbase

(const ptrbase&)=

delete

; ptrbase&

operator=(

const ptrbase&)=

delete;}

;template

<

typename t>

class

:public ptrbase

t*get()

~()private

: t* m_p;};

class

ptr:

public ptrbase

template

<

typename t>

ptr(t*

&& p)

template

<

typename t>

void

operator&=

(t*& pdst)

catch

(const std::bad_cast & e)}~

ptr(

)private

: ptrbase* m_ptr;};

intmain()

上述**在之前的基礎上簡單實現了乙個記憶體管理的單例類,配合新的記憶體分配和釋放函式new和delete使用,new開闢記憶體時,將指標和記憶體進行雙向繫結,delete時,將指標和記憶體解繫結。ptr類也會在指標拷貝時,將新指標和記憶體雙向繫結。

這樣,使用者可以對任意指標使用delete,如果delete的堆指標是函式引數指標或者是使用者通過 「=」賦值的指標,這些指標未提交到memorymanager中,因此,不會真正釋放記憶體。使用者需要關注的只是,一旦你的**中有指標,在超出作用域的時候,記得delete它就可以了。

注意:使用時,需要遵循:使用new函式代替new表示式開闢記憶體,使用delete函式代替delete表示式。

上述**只是簡單實現,它還存在如下問題:

new和delete為新增函式,不符合new和delete的使用習慣;

memorymanager類中所有指標統一存到vector中,將其按堆記憶體位址分組存放比較好,指向同一塊堆記憶體的指標位址放到一起,這樣甚至operator new和operator delete中都不需要引用計數,因為memorymanager中相當於進行了引用計數。

上述**還沒有解決忘記delete或者程式中發生異常,處理異常的**中未delete帶來的記憶體洩露問題。解決這個需要在2的基礎上,在memorymanager類增加乙個執行緒函式,定期掃瞄所有指標是否依然有效,移除無效的指標,並減少引用計數。

還有陣列開闢和釋放的operator new以及operator delete沒有改造。

總之,上述方案還不是盡善盡美,畢竟,我們為了保留操作習慣,最終得到的還是原始指標,c++原始指標和"="賦值運算子以及new,delete不具備自動記憶體管理的功能。本次改進,暫時告一段落,有興趣的同學可以自己繼續改進。

至於上述總結的問題為什麼我不解決掉,我個人覺得,c++記憶體管理是乙個很複雜的東西,c++本身又以高效著稱。正常情況下,上述方案已經將記憶體管理的複雜度降低到了我能掌控的程度,再增加乙個執行緒來管理記憶體,收益不大,反而降低效率,而且,還會帶來未盡的問題。

其實,我覺得,在new和delete中增加對記憶體標誌字串heap_sign_str都沒有必要,因為棧指標一般生存期很短,大多是函式區域性變數指標,這種情況很容易管理,我們不太可能會忘記其到底是不是堆記憶體指標,不太可能對其誤用delete。

最後,由於增加了new和delete函式,其實使用起來不如new和delete習慣,在都不習慣的條件下,其實我們可以使用c++11新增的智慧型指標來自動管理記憶體的。也就不需要向上面這樣一頓折騰。

關於 記憶體洩漏,野指標

記憶體洩漏 動態申請的記憶體空間沒有正常釋放,但也不能繼續使用 char a new char a char b new char b a b a 的空間未被釋放,也無法訪問,形成記憶體洩漏 基類的析構函式應設定為virtual,否則基類指標指向子類物件,呼叫基類析構時不會呼叫子類的析構函式,而造成...

記憶體洩漏與野指標

當你要為變數a申請一塊記憶體空間的時候,需要用到malloc函式。如何申請這塊記憶體空間呢?這就變成程式的工作了。程式首先會向系統申請一塊記憶體空間,然後程式會把申請好的這塊記憶體空間的首位址賦給變數a,假設這時候變數a拿到的首位址為0x1234,如果我們現在要對變數a進行迴圈操作,那麼每迴圈一次,...

安全程式設計 c 野指標和記憶體洩漏

儘管c 野指標和記憶體洩漏一直被詬病,但是在實時性很強的應用場合,c 仍然是不二之選。遊戲伺服器開發仍然使用c 作為主語言,但是大多結合動態指令碼技術,一方面規避了野指標和記憶體洩露,一方面獲得了開發效率和擴充套件性的紅利。但指令碼技術不是本文的討論重點,事實上關於c 與 lua的技術文章我也一直在...