Variant 与 内存泄露
http://blog.chinaunix.net/uid-10386087-id-2959221.html
今天遇到一個內(nèi)存泄露的問題。是師兄檢測出來的。Variant類型在使用后要Clear否則會造成內(nèi)存泄露,為什么呢?
Google一下找到下面一篇文章,主要介紹了Com的內(nèi)存泄露,中間有對Variant的一些解釋吧。
1. 引用計數(shù)泄漏
由于C++的一些對象生命周期難以管理,在COM中加入了引用計數(shù),用來解決這個問題,引用計數(shù)是COM最重要的特性 之一。但諷刺的是,雖然很多COM組件是用C++寫的,但是在所有編程語言中,C++使用COM是最麻煩的,而且最容易產(chǎn)生引用計數(shù)的泄漏,從而最終造成 內(nèi)存泄漏。
引用計數(shù)泄漏一旦產(chǎn)生,比new了之后忘了delete還難以定位,因為一個對象可能被很多地方使用,根本就不知道到底是那個使用的地方在 AddRef之后沒有Release,只能知道對象泄漏了,但不知道是哪里導(dǎo)致泄漏。
因此在使用COM的時候,盡量使用智能指針,而且最好是所有使用COM的地方全部使用智能指針,這樣能在很大成都上防止產(chǎn)生COM對象的引用計數(shù)泄 漏。
然而使用了智能指針一定能解決問題嗎?也不一定,至少有以下兩種情況,使用了智能指針仍然會產(chǎn)生引用計數(shù)泄漏,而且比上面的情況更加難以找到原因:
對象循環(huán)引用導(dǎo)致引用計數(shù)泄漏
如果有兩個對象A和B,在A中使用了B,同時在B中使用了A,B對象中有一個智能指針指向A,A對象中有一 個智能指針指向B,這時候就會產(chǎn)生循環(huán)引用導(dǎo)致。因為A對象能被析構(gòu)的前提是B對象先析構(gòu),而B對象析構(gòu)的前提同樣是A對象先析構(gòu),這就成了一個死局,兩 個對象都無法析構(gòu),A和B都產(chǎn)生了內(nèi)存泄漏。
要想解決這種問題,一種方法是把這個循環(huán)的鏈打斷,在某個合適的地方把某一個對象中的智能指針顯示地把智能指針賦值為NULL,這樣循環(huán)的條件被打 破,兩個對象就都可以析構(gòu)了。另一種方法是使用弱引用,自動完成。但弱引用相對有點(diǎn)復(fù)雜,在本文中不作介紹,讀者可以自行在網(wǎng)上搜索相關(guān)資料。
不正確的智能指針使用方法導(dǎo)致引用計數(shù)泄漏
有時候使用了智能指針,并且也沒有循環(huán)引用,但是對象仍然有引用計數(shù)泄漏,是不是很困惑?對于 這種情況,有可能是因為使用智能指針不正確引起的。
我們有時候會這樣使用智能指針:
pUnknown->QueryInterface(__uuidof(IMyInterface), & spMyInterface);
spMyInterface變量是一個智能指針,在大多數(shù)情況下這樣使用不會有任何問題,尤其是使用了微軟的_com_ptr_t或CComPtr 等。但如果我們使用一些第三方的智能指針,例如Boost里的intrusive_ptr。我們知道,在_com_ptr_t和CComPtr中,重載 了&操作符,在&操作符中,會先釋放目前的引用計數(shù),然后返回一個指向指針的指針。這樣帶來的好處是使用&操作符的時候,都會先 釋放當(dāng)前的對象,不會造成內(nèi)存泄漏,壞處是這個智能指針可能無法放到一些STL的容器中,因為有一些STL的容器,在移動容器中的數(shù)據(jù)單元時,會用 到&操作符,這樣的結(jié)果是一旦移動,對象就被釋放了。Boost的智能指針,例如intrusive_ptr是沒有重載&操作符的,因此 可以放心地在STL容器中使用,但如果寫類似于QueryInterface(__uuidof(IMyInterface), & spMyInterface)就要特別小心了,例如像下面的連續(xù)兩次QueryInterface用法,就會產(chǎn)生引用計數(shù)泄漏:
boost::intrusive_ptr??? spMyInterface;
pUnknown->QueryInterface(__uuidof(IMyInterface), & spMyInterface);
pUnknown2->QueryInterface(__uuidof(IMyInterface), & spMyInterface);
因為第二次調(diào)用QueryInterface之前,spMyInterface并沒有Release,因此就有引用計數(shù)泄漏了。如果在某個循環(huán)中這 樣使用,則問題會更加嚴(yán)重。
要解決這類問題,有兩種方法。第一是同一個變量永遠(yuǎn)不要多次使用,在變量聲明周期中類似的操作只使用一次,如果有多次使用的需求,用別的變量來實(shí) 現(xiàn)。另一種方法是每次使用之前,都先顯示地把變量清空,釋放引用計數(shù),例如先spMyInterface,再調(diào)用QueryInterface。
2. 字符串(BSTR)泄漏
字符串泄漏容易被忽視,而且COM中使用的BSTR字符串,并不是用new或者malloc等來分配的,而 是用類似于::SysAllocString等API來分配的,要檢測是否有泄漏更加困難。
我們一般在調(diào)用某個類似于這樣的函數(shù)GetString([out] BSTR * str)的返回BSTR的指針的函數(shù)后,需要調(diào)用::SysFreeString把字符串釋放掉,和new/delete、malloc/free、 AddRef/Release類似。如果忘記調(diào)用::SysFreeString,就會產(chǎn)生內(nèi)存泄漏。
一般我們使用字符串類來自動釋放,例如使用CComBSTR,就可以這樣寫:
CComBSTR str;
GetString(&str);
str在析構(gòu)的時候會自動調(diào)用::SysFreeString把字符串釋放掉。
但如果看一下CComBSTR類的實(shí)現(xiàn),我們會發(fā)現(xiàn),這個類和Boost的intrusive_ptr類似,是沒有重載&操作符的,這就帶 來和intrusive_ptr一樣的問題,如果連續(xù)兩次以上使用這個字符串,就會產(chǎn)生內(nèi)存泄漏,例如字符串類我們有時候會這樣用:
CComBSTR str;
for(int I = 0;I < 100;I ++)
{
pInterface->GetString(&str);
// do something
}
這樣的后果是每調(diào)用一次,就會 產(chǎn)生一個內(nèi)存泄漏。因此一定要保證在字符串對象的生命周期中,只被使用一次,代碼改為這樣就不會有問題:
for(int I = 0;I < 100;I ++)
{
CComBSTR str;
pInterface->GetString(&str);
// do something
}
另外還有一種方法是使用_bstr_t的GetAddress函數(shù),這個函數(shù)返回的是 BSTR *,每次調(diào)用里面都會先釋放當(dāng)前的字符串,因此使用 _bstr_t屬于比較保險并且方便的選擇。不過遺憾的是在VC6中這個類并沒有實(shí)現(xiàn)GetAddress函數(shù),用起來可能會影響代碼的移植性。
3. VARIANT泄漏
這個情況和字符串泄漏類似,變量不再使用之后,應(yīng)該調(diào)用::VariantClear來釋放內(nèi)存,否則就可能會 產(chǎn)生內(nèi)存泄漏。
解決方法也和字符串類似,可以使用VARIANT的CComVariant,但這個類和CComBSTR也有一樣的問題,因此使用的時候也一樣要注 意,必須保證每個變量的生命周期中只使用一次。
另外有一個和_bstr_t類似的類:_variant_t,里面有一個GetAddress函數(shù)可以比較方便和安全的使用。具體的細(xì)節(jié)請參考這幾 個類的實(shí)現(xiàn)代碼,這里不多做介紹。
4. 總結(jié)
對于本文中提到的幾種內(nèi)存泄漏的原因,根源在于使用一個第三方的類的時候,其實(shí)并沒有真正理解這些類的實(shí)現(xiàn)原理,我們以為正確 地使用了,但其實(shí)用法并不對。因此在使用一個不是自己寫的第三方類的時候,不能直接拿過來就用,而是應(yīng)該花一點(diǎn)時間去仔細(xì)了解一下這個類,知道怎樣使用是 合理的,而怎樣使用是不正確的,把所有的隱患在編碼之前就解決掉,整個項目開發(fā)過程就會更加順利,產(chǎn)品質(zhì)量就會更高。
關(guān)于第三方類庫的使用,在本人另一篇博文《編碼原則十日談》中有提及,這里給出地址,供讀者參考。
總結(jié)
以上是生活随笔為你收集整理的Variant 与 内存泄露的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: android实现重复动画,androi
- 下一篇: Android普通对话框标题居中,and