Variant 与 内存泄露

简介: http://blog.chinaunix.net/uid-10386087-id-2959221.html今天遇到一个内存泄露的问题。

http://blog.chinaunix.net/uid-10386087-id-2959221.html


今天遇到一个内存泄露的问题。是师兄检测出来的。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->QueryInterface(__uuidof(IMyInterface), & spMyInterface);

spMyInterface变量是一个智能指针,在大多数情况下这样使用不会有任何问题,尤其是使用了微软的_com_ptr_t或CComPtr 等。但如果我们使用一些第三方的智能指针,例如Boost里的intrusive_ptr。我们知道,在_com_ptr_t和CComPtr中,重载 了&操作符,在&操作符中,会先释放目前的引用计数,然后返回一个指向指针的指针。这样带来的好处是使用&操作符的时候,都会先 释放当前的对象,不会造成内存泄漏,坏处是这个智能指针可能无法放到一些STL的容器中,因为有一些STL的容器,在移动容器中的数据单元时,会用 到&操作符,这样的结果是一旦移动,对象就被释放了。Boost的智能指针,例如intrusive_ptr是没有重载&操作符的,因此 可以放心地在STL容器中使用,但如果写类似于QueryInterface(__uuidof(IMyInterface), & spMyInterface)就要特别小心了,例如像下面的连续两次QueryInterface用法,就会产生引用计数泄漏:

boost::intrusive_ptr    spMyInterface;

pUnknown->QueryInterface(__uuidof(IMyInterface), & spMyInterface);

pUnknown2->QueryInterface(__uuidof(IMyInterface), & spMyInterface);

因为第二次调用QueryInterface之前,spMyInterface并没有Release,因此就有引用计数泄漏了。如果在某个循环中这 样使用,则问题会更加严重。

要解决这类问题,有两种方法。第一是同一个变量永远不要多次使用,在变量声明周期中类似的操作只使用一次,如果有多次使用的需求,用别的变量来实 现。另一种方法是每次使用之前,都先显示地把变量清空,释放引用计数,例如先spMyInterface,再调用QueryInterface。

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 ++)

{

pInterface->GetString(&str);

// do something

}

这样的后果是每调用一次,就会 产生一个内存泄漏。因此一定要保证在字符串对象的生命周期中,只被使用一次,代码改为这样就不会有问题:

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

{

CComBSTR str;

pInterface->GetString(&str);

// do something

}

另外还有一种方法是使用_bstr_t的GetAddress函数,这个函数返回的是 BSTR *,每次调用里面都会先释放当前的字符串,因此使用 _bstr_t属于比较保险并且方便的选择。不过遗憾的是在VC6中这个类并没有实现GetAddress函数,用起来可能会影响代码的移植性。

3. VARIANT泄漏
这个情况和字符串泄漏类似,变量不再使用之后,应该调用::VariantClear来释放内存,否则就可能会 产生内存泄漏。

解决方法也和字符串类似,可以使用VARIANT的CComVariant,但这个类和CComBSTR也有一样的问题,因此使用的时候也一样要注 意,必须保证每个变量的生命周期中只使用一次。

另外有一个和_bstr_t类似的类:_variant_t,里面有一个GetAddress函数可以比较方便和安全的使用。具体的细节请参考这几 个类的实现代码,这里不多做介绍。

4. 总结
对于本文中提到的几种内存泄漏的原因,根源在于使用一个第三方的类的时候,其实并没有真正理解这些类的实现原理,我们以为正确 地使用了,但其实用法并不对。因此在使用一个不是自己写的第三方类的时候,不能直接拿过来就用,而是应该花一点时间去仔细了解一下这个类,知道怎样使用是 合理的,而怎样使用是不正确的,把所有的隐患在编码之前就解决掉,整个项目开发过程就会更加顺利,产品质量就会更高。

关于第三方类库的使用,在本人另一篇博文《编码原则十日谈》中有提及,这里给出地址,供读者参考。


目录
相关文章
|
4月前
|
存储
阿里云轻量应用服务器收费标准价格表:200Mbps带宽、CPU内存及存储配置详解
阿里云香港轻量应用服务器,200Mbps带宽,免备案,支持多IP及国际线路,月租25元起,年付享8.5折优惠,适用于网站、应用等多种场景。
1077 0
|
4月前
|
存储 缓存 NoSQL
内存管理基础:数据结构的存储方式
数据结构在内存中的存储方式主要包括连续存储、链式存储、索引存储和散列存储。连续存储如数组,数据元素按顺序连续存放,访问速度快但扩展性差;链式存储如链表,通过指针连接分散的节点,便于插入删除但访问效率低;索引存储通过索引表提高查找效率,常用于数据库系统;散列存储如哈希表,通过哈希函数实现快速存取,但需处理冲突。不同场景下应根据访问模式、数据规模和操作频率选择合适的存储结构,甚至结合多种方式以达到最优性能。掌握这些存储机制是构建高效程序和理解高级数据结构的基础。
315 1
|
4月前
|
存储 弹性计算 固态存储
阿里云服务器配置费用整理,支持一万人CPU内存、公网带宽和存储IO性能全解析
要支撑1万人在线流量,需选择阿里云企业级ECS服务器,如通用型g系列、高主频型hf系列或通用算力型u1实例,配置如16核64G及以上,搭配高带宽与SSD/ESSD云盘,费用约数千元每月。
321 0
|
存储 编译器 C语言
【C语言篇】数据在内存中的存储(超详细)
浮点数就采⽤下⾯的规则表⽰,即指数E的真实值加上127(或1023),再将有效数字M去掉整数部分的1。
875 0
|
存储
共用体在内存中如何存储数据
共用体(Union)在内存中为所有成员分配同一段内存空间,大小等于最大成员所需的空间。这意味着所有成员共享同一块内存,但同一时间只能存储其中一个成员的数据,无法同时保存多个成员的值。
|
存储 C语言
数据在内存中的存储方式
本文介绍了计算机中整数和浮点数的存储方式,包括整数的原码、反码、补码,以及浮点数的IEEE754标准存储格式。同时,探讨了大小端字节序的概念及其判断方法,通过实例代码展示了这些概念的实际应用。
888 1
|
存储 编译器
数据在内存中的存储
数据在内存中的存储
135 4
|
存储 弹性计算 算法
前端大模型应用笔记(四):如何在资源受限例如1核和1G内存的端侧或ECS上运行一个合适的向量存储库及如何优化
本文探讨了在资源受限的嵌入式设备(如1核处理器和1GB内存)上实现高效向量存储和检索的方法,旨在支持端侧大模型应用。文章分析了Annoy、HNSWLib、NMSLib、FLANN、VP-Trees和Lshbox等向量存储库的特点与适用场景,推荐Annoy作为多数情况下的首选方案,并提出了数据预处理、索引优化、查询优化等策略以提升性能。通过这些方法,即使在资源受限的环境中也能实现高效的向量检索。
536 1
|
存储 Java
JVM知识体系学习四:排序规范(happens-before原则)、对象创建过程、对象的内存中存储布局、对象的大小、对象头内容、对象如何定位、对象如何分配
这篇文章详细地介绍了Java对象的创建过程、内存布局、对象头的MarkWord、对象的定位方式以及对象的分配策略,并深入探讨了happens-before原则以确保多线程环境下的正确同步。
216 0
JVM知识体系学习四:排序规范(happens-before原则)、对象创建过程、对象的内存中存储布局、对象的大小、对象头内容、对象如何定位、对象如何分配

热门文章

最新文章

下一篇
开通oss服务