BSTR使用误区以及隐藏的内存破坏和内存泄漏

作者:magictong

简介

BSTR的数据结构是什么样子并不是本文讨论的问题,但是却是本文的基础。在解决COM的跨平台编程的问题时,需要定义一种通用的字符串类型,它就这样被发明了,而且它的结构很容易匹配到不同的编程环境中,对于C++程序员来说,要记住的最基本的一点就是分配BSTR结构时,并不是简单的调用new、malloc就可以完成的,而且大部分的字符串相关的API和C库函数也是不能用于处理BSTR的,其实这也是使用BSTR的误区之一,在C++里面,BSTR被简单的define为wchar_t*,这也是容易引起误会的原因之一。

今天注意讨论一下BSTR作为函数的参数、返回值时,调用者和被调用者操作BSTR时扮演的不同角色问题。首先需要注意的时,在必须使用BSTR时尽量使用BSTR的包装类CComBSTR,它会给你额外完成一些资源的管理工作,令你轻松不少,出错的可能性也就大大降低了。

像一个很简单自然的用法:BSTR bstrInfo = L””,其实是错误的(当然如果你说我以后就把bstrInfo完全当成wchar_t*来使用,那我无话可说)。对于一个BSTR变量来说,它只可能是NULL或者正确分配的BSTR指针。

BSTR当成参数和返回使用的基本约定

1、[in\out]参数

譬如函数:void GetChangBSTR(BSTR* pbstrTitle),在GetChangBSTR函数内部需要先读取pbstrTitle的值使用,然后改变pbstrTitle的值。

这种情况下被调用者(也就是GetChangBSTR)在给pbstrTitle赋新值前,需要先释放pbstrTitle里面原来的值,然后再给pbstrTitle分配上新的值。

而调用者(也就是调用GetChangBSTR的函数),在传参之前需要先给pbstrTitle赋值上正确的值,调用结束之后还需要再释放pbstrTitle的值。

2、[in]参数

譬如函数:void PutText(BSTR bstrText),在PutText内部仅需读取bstrText的值。

这种情况下被调用者可以随意读取bstrText的值,不需要做其它操作。

而调用者,在传参之前需要先给bstrText赋上正确的值,调用结束后需要释放bstrText的值。

3、[out]参数

譬如函数:void GetText(BSTR* pbstrText),在GetText内部直接给pbstrText赋值。

这种情况下被调用者直接给pbstrText赋值即可,不需要做其它操作。

而调用者,在传参之前不能给pbstrText赋值,调用结束后需要释放pbstrText的值。

4、返回参数

譬如函数:BSTR GetText(),在GetText内部会返回一个BSTR出来。调用者直接返回一个有效的BSTR即可,而调用者需要释放这个返回的BSTR。

BSTR当成参数和返回使用的误区

1、[in\out]参数

这种情况下,被调用者如果没有给参数赋值,不要释放原始值,因为根据约定调用者还会释放一次,这样会造成多次释放,可能导致内存破坏。

void GetChangBSTR(/*[in\out] */BSTR* pbstrTitle)

{

// using the bs here

DoSomething(*pbstrTitle);

if (...)

{

::SysReAllocString(*pbstrTitle, _T("Tecnet"));

}

else

{

::SysFreeString(*pbstrTitle); // 这里的做法是错误的。

}

return;

}

2、[in]参数

被调用者不要对参数进行释放操作,原因和上面相同,调用者还会重复释放一次,可能导致内存破坏。

void PutText(/*[in] */BSTR bstrText)

{

// using the bs here

DoSomething(bstrText);

::SysFreeString(bstrText); // 这里的做法是错误的。

return;

}

3、[out]参数

如果调用者在传参之前给参数赋值,参数传递给被调用者之后,在改变值之前是没有释放操作的,也就是说会有内存泄漏。

void GetText(/*[out] */BSTR* pbstrText)

{

::SysAllocString(*pbstrText, _T("Tecnet"));

return;

}

// use GetText

BSTR bstrText;

::SysAllocString(bstrText, _T("qq"));

GetText(&bstrText);

::SysFreeString(bstrText); // 很不幸,这里实际上只释放了一次

4、返回参数

被调用者不要释放(不管是直接还是间接导致的)返回给调用者的BSTR,因为调用者会释放。

BSTR GetText()

{

BSTR bstrText = ::SysAllocString(bstrText, _T("Tecnet"));

::SysFreeString(bstrText); // 这里释放就悲剧了

return bstrText;

}

// use GetText

BSTR bstrText = GetText(&bstrText);

// use bstrText

DoSome(bstrText); // bstrText已经被释放,使用是有问题的

::SysFreeString(bstrText); // 这就不仅仅是重复释放的问题了

BSTR在类里面使用的误区

1、我想把某个[in]参数BSTR保存到某个类成员变量

这种情况下,直接赋值是不行的,因为外面调用者会释放这个BSTR参数,因此要保存的话,需要类函数自己重新申请一个新的BSTR。

void CSomeClass::SetText(BSTR bs)

{

// m_bstrText是CSomeClass的成员变量

m_bstrText = bs; // 错误做法

m_bstrText = ::SysReAllocString(bs); // 正确做法

}

2、我想传出一个类的BSTR成员变量

同样的道理,因为外面可能在某个时间释放传出的BSTR变量,因此要防止类成员变量被无辜释放,需要生成一个有效的拷贝,再传出。

void CSomeClass::GetText(BSTR& bs)

{

// m_bstrText是CSomeClass的成员变量

bs = m_bstrText; // 错误做法

bs = ::SysAllocString(m_bstrText); // 正确做法

}

BSTR的封装类CComBSTR

微软发现我们使用BSTR有上面的种种不爽,因此决定对其进行封装,很贴心吧!嗯,确实贴心,其中比较好的一个封装就是CComBSTR(很多项目组可能有自己的BSTR封装,但是其实都是大同小异的),这个封装类确实很好用(虽然没有提供CString那么多牛皮的功能),使用很方便,但是,如果我们错误使用也会产生噩梦,而且错误很难查找,我们来点评几个(注意:下面的内容需要对CComBSTR封装的基本原理和提供接口有一定了解,但这些并不是本文要讨论的内容,另外一个封装类是_bstr_t,它是用引用计数来管理的,实现比CComBSTR复杂很多,个人不太建议使用_bstr_t)。

1、被调用者违反out参数使用约定

void GetText(/*[out]*/BSTR& bstrText)

{

CComBSTR bstrT(_T("qqpcmgr"));

// 错误:bstrT会被自动释放,违反了out参数的使用约定

bstrText = (BSTR)bstrT;

return;

//////////////////////////////////////////////////////////////////////////

// 正确的做法,一般来说Detach是效率更好的方法

// 但是如果bstrT本身是一个类成员变量,可能要用Copy

bstrText = bstrT.Copy();

bstrText = bstrT.Detach();

return;

}

2、调用者违反out参数使用约定

void GetText(/*[out]*/BSTR& bstrText)

{

// ……

}

// use GetText

CComBSTR bstrText(L"qq");

// 内存泄漏,调用GetText前要先清空bstrText

// bstrText.Empty();

GetText(bstrText);

3、看一个隐晦一点的

void GetText(/*[out]*/BSTR& bstrText)

{

// ……

}

// use GetText

CComBSTR bstrText;

BSTR bstrInfo = NULL;

GetText(bstrInfo);

// 如果后面没有显示释放bstrInfo

// 这里就会有内存泄漏,这种混用也是比较危险的

bstrText = bstrInfo;

// 如果你想CComBSTR接管一个BSTR,可以使用

// bstrText.Attach(bstrInfo);

4、重复释放,造成内存破坏

{

CComBSTR bstrText(L"Tencent");

// 因为CComBSTR重载了operator BSTR操作,因此这里是支持的

::SysFreeString(bstrText); // 错误做法,如果你确实想释放,可以调用Empty

}

// 超出bstrText范围,bstrText会被自动释放,可能导致内存破坏

// ……

参考文献

[1] BSTR https://zh.wikipedia.org/zh-cn/BSTR

[2] BSTR_INSIDE http://wenku.baidu.com/view/d577a1c5d5bbfd0a795673b2.html

http://blog.csdn.net/magictong/article/details/8995516

时间: 2024-08-05 11:12:36

BSTR使用误区以及隐藏的内存破坏和内存泄漏的相关文章

Android应用性能优化系列视图篇——隐藏在资源图片中的内存杀手

图片加载性能优化永远是Android领域中一个无法绕过的话题,经过数年的发展,涌现了很多成熟的图片加载开源库,比如Fresco.Picasso.UIL等等,使得图片加载不再是一个头疼的问题,并且大幅降低了OOM发生的概率.然而,在图片加载方面我们是否可以就此放松警惕了呢? 开源图片加载库能为我们解决绝大部分有关图片的问题,然而并不是所有! 首先,图片从来源上可以分成三大类:网络图片.手机图片.APK资源图片.网络图片和手机图片都在图片加载库功能的覆盖范围内,基本上不用开发者太操心,但是APK资源

PHP “substr_replace()”释放后重用远程内存破坏漏洞

PHP是广泛使用的通用目的脚本语言,特别适合于Web开发,可嵌入到HTML中. PHP的"substr_replace()"函数在实现上存在释放后重用远程内存破坏漏洞,远程攻击者可利用此漏洞在网络服务器中执行任意代码,造成拒绝服务. 此漏洞源于在将同一个变量多次发送到"substr_replace()"函数时,PHP会使该函数中的三个变量使用同一个指针,所以当函数中的类型转换更改了该指针,该指针也会使其他变量无效. 解决方法 PHP --- 建议升级PHP产品至最新

浅谈C语言内存管理、内存泄露、堆栈

1.内存分配区间: 对于一个C语言程序而言,内存空间主要由五个部分组成:代码段(.text).数据段(.data).静态区(.BSS).堆和栈组成. BSS段:BSS段(bss segment)通常是指用来存放程序中未初始化的全局变量和静态变量 (这里注意一个问题:一般的书上都会说全局变量和静态变量是会自动初始化的,那么哪来的未初始化的变量呢?变量的初始化可以分为显示初始化和隐式初始化,全局变量和静态变量如果程序员自己不初始化的话的确也会被初始化,那就是不管什么类型都初始化为0,这种没有显示初始

Andfroid 内存溢出与内存泄漏的简单分析与解决

<一>内存溢出与内存泄露 首先我们要知道内存溢出与内存泄露的概念,什么是内存溢出和内存泄露. 内存溢出:就想杯子里得水满了,就溢出了.内存溢出就是分配的内存被用光了,不够用了. 内存泄露:就如同杯子里面有石子,导致杯子里面的一部分空间没有被利用,在APP中内存泄露就是指该被回收的内存没有被回收,导致一部分内存一直被占着,可利用内存变少了.当泄露过多 时,可利用的内存越来越少,就会引起内存溢出了. <二> 查找内存泄露与内存溢出 (1) 内存溢出,最明显的地方就是报错,APP奔溃并报

c/c++深入篇之内存分配与内存对齐的探讨

 不明白内存分配和指针的可以看看,其实这本是我们老师留的一个操作系统科技小论文作业,不知道写什么,干脆把以前收藏的经典C内存分配的文章整理并修改了一下.       此文章有2个用处,1:这是个小论文,格式完整,大家可以复制回去交作业:2:这是整理的经典C内存分配小教程(也加了些我自己的观点),不明白内存分配的可以看看. 还有很重要的一个问题:      这篇文章引用的很多内容我也不知道究竟是出自谁手,知道作者是谁的麻烦告诉下,我好谢谢他.(记得都是csdn里面找的) tag: 操作系统 论

内存管理 浅析 内存管理/内存优化技巧

内存管理 浅析 下列行为都会增加一个app的内存占用: 1.创建一个OC对象: 2.定义一个变量: 3.调用一个函数或者方法. 如果app占用内存过大,系统可能会强制关闭app,造成闪退现象,影响用户体验.如何让回收那些不再使用的对象呢?本文着重介绍OC中的内存管理. 所谓内存管理,就是对内存进行管理,涉及的操作有: 1.分配内存:比如创建一个对象,会增加内存占用: 2.清除内存:比如销毁一个对象,会减少内存占用. 内存管理的管理范围: 1.任何继承了NSObject的对象: 2.对其他非对象类

android内存优化之三内存分析工具的使用

 anroid内存分析工具的使用 一.Eclipse Heap分析内存泄露 Android开发中避免不了碰到内存泄露问题,这里先大概讲下内存泄露的基本概念:内存泄露官方的解释是是用动态存储分配函数动态开辟的空间,在使用完毕后未释放,结果导致一直占据该内存单元,直到程序结束.它也可以理解为new的新对象用完后,该对象没有得到回收,造成的无用的对象一直占据着内存,这种无用的随着操作的次数越多,占据的内存越多,直到内存溢出程序,报错停止运行.内存溢出问题比起程序直接报错的问题更难定位,光靠阅读代码来分

Java的内存管理与内存泄露

作为Internet最流行的编程语言之一,Java现正非常流行.我们的网络应用程序就主要采用Java语言开发,大体上分为客户端.服务器和数据库三个层次.在进入测试过程中,我们发现有一个程序模块系统内存和CPU资源消耗急剧增加,持续增长到出现java.lang.OutOfMemoryError为止.经过分析Java内存泄漏是破坏系统的主要因素.这里与大家分享我们在开发过程中遇到的Java内存泄漏的检测和处理解决过程. 本文先介绍Java的内存管理,以及导致Java内存泄露的原因. 一. Java是

C++内存机制中内存溢出、内存泄露、内存越界和栈溢出的区别和联系

当我们在用C++做底层驱动的时候,经常会遇到内存不足的警告,究其原因,往往是因为内存出现溢出,泄露或者越界等原因.那么他们之间有什么联系吗? 内存溢出(out of memory) 是指程序在申请内存时,没有足够的内存空间供其使用. 内存泄漏(memory leak) 是指程序在申请内存后,无法释放已申请的内存空间,占用有用内存. 注:内存泄漏最终会导致内存溢出 简单理解,内存溢出就是要求分配的内存超出了系统所给的.内存泄漏是指向系统申请分配内存进行使用(new),但是用完后不归还(delete