Variant 與 記憶體洩露

2021-07-28 16:58:35 字數 3389 閱讀 9582

今天遇到乙個記憶體洩露的問題。是師兄檢測出來的。variant型別在使用後要clear否則會造成記憶體洩露,為什麼呢?

google一下找到下面一篇文章,主要介紹了com的記憶體洩露,中間有對variant的一些解釋吧。

1. 引用計數洩漏

由於c++的一些物件生命週期難以管理,在com中加入了引用計數,用來解決這個問題,引用計數是com最重要的特性 之一。但諷刺的是,雖然很多com元件是用c++寫的,但是在所有程式語言中,c++使用com是最麻煩的,而且最容易產生引用計數的洩漏,從而最終造成 記憶體洩漏。

引用計數洩漏一旦產生,比new了之後忘了delete還難以定位,因為乙個物件可能被很多地方使用,根本就不知道到底是那個使用的地方在 addref之後沒有release,只能知道物件洩漏了,但不知道是**導致洩漏。

因此在使用com的時候,盡量使用智慧型指標,而且最好是所有使用com的地方全部使用智慧型指標,這樣能在很大成都上防止產生com物件的引用計數洩 漏。

然而使用了智慧型指標一定能解決問題嗎?也不一定,至少有以下兩種情況,使用了智慧型指標仍然會產生引用計數洩漏,而且比上面的情況更加難以找到原因:

物件迴圈引用導致引用計數洩漏

如果有兩個物件a和b,在a中使用了b,同時在b中使用了a,b物件中有乙個智慧型指標指向a,a物件中有一 個智慧型指標指向b,這時候就會產生迴圈引用導致。因為a物件能被析構的前提是b物件先析構,而b物件析構的前提同樣是a物件先析構,這就成了乙個死局,兩 個物件都無法析構,a和b都產生了記憶體洩漏。

要想解決這種問題,一種方法是把這個迴圈的鏈打斷,在某個合適的地方把某乙個物件中的智慧型指標顯示地把智慧型指標賦值為null,這樣迴圈的條件被打 破,兩個物件就都可以析構了。另一種方法是使用弱引用,自動完成。但弱引用相對有點複雜,在本文中不作介紹,讀者可以自行在網上搜尋相關資料。

不正確的智慧型指標使用方法導致引用計數洩漏

有時候使用了智慧型指標,並且也沒有迴圈引用,但是物件仍然有引用計數洩漏,是不是很困惑?對於 這種情況,有可能是因為使用智慧型指標不正確引起的。

我們有時候會這樣使用智慧型指標:

punknown->queryinte***ce(__uuidof(imyinte***ce), & spmyinte***ce);

spmyinte***ce變數是乙個智慧型指標,在大多數情況下這樣使用不會有任何問題,尤其是使用了微軟的_com_ptr_t或ccomptr 等。但如果我們使用一些第三方的智慧型指標,例如boost裡的intrusive_ptr。我們知道,在_com_ptr_t和ccomptr中,過載 了&操作符,在&操作符中,會先釋放目前的引用計數,然後返回乙個指向指標的指標。這樣帶來的好處是使用&操作符的時候,都會先 釋放當前的物件,不會造成記憶體洩漏,壞處是這個智慧型指標可能無法放到一些stl的容器中,因為有一些stl的容器,在移動容器中的資料單元時,會用 到&操作符,這樣的結果是一旦移動,物件就被釋放了。boost的智慧型指標,例如intrusive_ptr是沒有過載&操作符的,因此 可以放心地在stl容器中使用,但如果寫類似於queryinte***ce(__uuidof(imyinte***ce), & spmyinte***ce)就要特別小心了,例如像下面的連續兩次queryinte***ce用法,就會產生引用計數洩漏:

boost::intrusive_ptr    spmyinte***ce;

punknown->queryinte***ce(__uuidof(imyinte***ce), & spmyinte***ce);

punknown2->queryinte***ce(__uuidof(imyinte***ce), & spmyinte***ce);

因為第二次呼叫queryinte***ce之前,spmyinte***ce並沒有release,因此就有引用計數洩漏了。如果在某個迴圈中這 樣使用,則問題會更加嚴重。

要解決這類問題,有兩種方法。第一是同乙個變數永遠不要多次使用,在變數宣告週期中類似的操作只使用一次,如果有多次使用的需求,用別的變數來實 現。另一種方法是每次使用之前,都先顯示地把變數清空,釋放引用計數,例如先spmyinte***ce,再呼叫queryinte***ce。

2. 字串(bstr)洩漏

字串洩漏容易被忽視,而且com中使用的bstr字串,並不是用new或者malloc等來分配的,而 是用類似於::sysallocstring等api來分配的,要檢測是否有洩漏更加困難。

我們一般在呼叫某個類似於這樣的函式getstring([out] bstr * str)的返回bstr的指標的函式後,需要呼叫::sysfreestring把字串釋放掉,和new/delete、malloc/free、 addref/release類似。如果忘記呼叫::sysfreestring,就會產生記憶體洩漏。

一般我們使用字串類來自動釋放,例如使用ccombstr,就可以這樣寫:

ccombstr str;

getstring(&str);

str在析構的時候會自動呼叫::sysfreestring把字串釋放掉。

但如果看一下ccombstr類的實現,我們會發現,這個類和boost的intrusive_ptr類似,是沒有過載&操作符的,這就帶 來和intrusive_ptr一樣的問題,如果連續兩次以上使用這個字串,就會產生記憶體洩漏,例如字串類我們有時候會這樣用:

ccombstr str;

for(int i = 0;i < 100;i ++)

這樣的後果是每呼叫一次,就會 產生乙個記憶體洩漏。因此一定要保證在字串物件的生命週期中,只被使用一次,**改為這樣就不會有問題:

for(int i = 0;i < 100;i ++)

另外還有一種方法是使用_bstr_t的getaddress函式,這個函式返回的是 bstr *,每次呼叫裡面都會先釋放當前的字串,因此使用 _bstr_t屬於比較保險並且方便的選擇。不過遺憾的是在vc6中這個類並沒有實現getaddress函式,用起來可能會影響**的移植性。

3. variant洩漏

這個情況和字串洩漏類似,變數不再使用之後,應該呼叫::variantclear來釋放記憶體,否則就可能會 產生記憶體洩漏。

解決方法也和字串類似,可以使用variant的ccomvariant,但這個類和ccombstr也有一樣的問題,因此使用的時候也一樣要注 意,必須保證每個變數的生命週期中只使用一次。

另外有乙個和_bstr_t類似的類:_variant_t,裡面有乙個getaddress函式可以比較方便和安全的使用。具體的細節請參考這幾 個類的實現**,這裡不多做介紹。

4. 總結

對於本文中提到的幾種記憶體洩漏的原因,根源在於使用乙個第三方的類的時候,其實並沒有真正理解這些類的實現原理,我們以為正確 地使用了,但其實用法並不對。因此在使用乙個不是自己寫的第三方類的時候,不能直接拿過來就用,而是應該花一點時間去仔細了解一下這個類,知道怎樣使用是 合理的,而怎樣使用是不正確的,把所有的隱患在編碼之前就解決掉,整個專案開發過程就會更加順利,產品質量就會更高。

關於第三方類庫的使用,在本人另一篇博文《編碼原則十日談》中有提及,這裡給出位址,供讀者參考。

Variant 與 記憶體洩露

今天遇到乙個記憶體洩露的問題。是師兄檢測出來的。variant型別在使用後要clear否則會造成記憶體洩露,為什麼呢?google一下找到下面一篇文章,主要介紹了com的記憶體洩露,中間有對variant的一些解釋吧。1.引用計數洩漏 由於c 的一些物件生命週期難以管理,在com中加入了引用計數,用...

記憶體溢位與記憶體洩露

2019獨角獸企業重金招聘python工程師標準 兩者的區別 記憶體溢位 out of memory 指程式在申請記憶體時,沒有足夠的記憶體空間供其使用,出現out of memory 比如申請了乙個integer,但給它存了long才能存下的數,那就是記憶體溢位。記憶體洩露 memory leak...

記憶體溢位,與記憶體洩露

記憶體洩漏 memory leak 是指程式在申請記憶體後,無法釋放已申請的記憶體空間 只出不進漏氣一樣 記憶體溢位 就是你要的記憶體空間超過了系統實際分配的空間,此時系統相當於沒法滿足你的需求,就會報記憶體溢位的錯誤 比方說棧,棧滿時再做進棧必定產生空間溢位,叫上溢,棧空時再做退棧也產生空間溢位,...