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