BSTR使用误区以及隐藏的内存破坏和内存泄漏
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
BSTR使用误区以及隐藏的内存破坏和内存泄漏的更多相关文章
- Android应用性能优化系列视图篇——隐藏在资源图片中的内存杀手
图片加载性能优化永远是Android领域中一个无法绕过的话题,经过数年的发展,涌现了很多成熟的图片加载开源库,比如Fresco.Picasso.UIL等等,使得图片加载不再是一个头疼的问题,并且大幅降 ...
- PHP Fileinfo组件越界内存破坏漏洞
漏洞版本: PHP PHP 5.x 漏洞描述: BUGTRAQ ID: 66002 CVE(CAN) ID: CVE-2014-2270 PHP是一种HTML内嵌式的语言. PHP的file程序在解析 ...
- C++内存管理2-内存泄漏
1 C++中动态内存分配引发问题的解决方案 假设我们要开发一个String类,它可以方便地处理字符串数据.我们可以在类中声明一个数组,考虑到有时候字符串极长,我们可以把数组大小设为200,但一般的情况 ...
- C++ Primer : 第十二章 : 动态内存之动态内存管理(new和delete)
C++语言定义了两个运算符来分配和释放动态内存:运算符new分配内存,运算符delete释放new分配的内存. 运算符new和delete 使用new动态分配和初始化对象 在自由空间分配的内存是无名的 ...
- Java的内存管理与内存泄露
作为Internet最流行的编程语言之一,Java现正非常流行.我们的网络应用程序就主要采用Java语言开发,大体上分为客户端.服务器和数据库三个层次.在进入测试过程中,我们发现有一个程序模块系统内存 ...
- C++内存机制中内存溢出、内存泄露、内存越界和栈溢出的区别和联系
当我们在用C++做底层驱动的时候,经常会遇到内存不足的警告,究其原因,往往是因为内存出现溢出,泄露或者越界等原因.那么他们之间有什么联系吗? 内存溢出(out of memory) 是指程序在申请内存 ...
- Linux内存描述之内存区域zone--Linux内存管理(三)
1 内存管理域zone 为了支持NUMA模型,也即CPU对不同内存单元的访问时间可能不同,此时系统的物理内存被划分为几个节点(node), 一个node对应一个内存簇bank,即每个内存簇被认为是一个 ...
- [C]内存管理、内存泄露、堆栈
原文地址:https://www.cnblogs.com/youthshouting/p/4280543.html,转载请注明源地址. 1.内存分配区间: 对于一个C语言程序而言,内存 ...
- 浅谈C语言内存管理、内存泄露、堆栈
1.内存分配区间: 对于一个C语言程序而言,内存空间主要由五个部分组成:代码段(.text).数据段(.data).静态区(.BSS).堆和栈组成. BSS段:BSS段 ...
随机推荐
- WPF 获得触摸精度和触摸点
原文:WPF 获得触摸精度和触摸点 本文主要告诉大家如何获得所有的触摸设备的触摸精度和触摸点数. 需要通过反射的方法才可以拿到触摸的精度. 使用 Tablet.TabletDevices 可以获得所有 ...
- APP压力測试新手教程
Daniel Knott 用过各种不同编程语言和软件质量保证工具.他在软件开发和測试方面干了七年,自2010年,他一直在德国汉堡的XING AG公司就职,几个项目里,比方XING调查和XING建议,他 ...
- ASP.Net Core 2.2使用SQLite数据库unable to open database file
原文:ASP.Net Core 2.2使用SQLite数据库unable to open database file 最近把项目更新到了ASP.Net Core 2.2,发布之后发现在IIS下使用SQ ...
- Android Studio:Grade 全局参数定义
Grade 全局参数定义 实际开发中设置公共的编译依赖参数等. 方法一: 在项目外层的build.gradle文件中定义,格式如下: 文件名:build.gradle ext { sourceComp ...
- 从编译,执行过程理解c#
上节我们说过C#所开发的程序源代码并不是编译成能够直接在操作系统上执行的二进制代码.与Java类似,它被编译成为中间代码,然后通过.NET Framework的虚拟机——被称之为通用语言运行时(CLR ...
- Matlab Tricks(二十八)—— 笛卡尔积的实现
笛卡尔积在数学上是一种二元关系,笛卡尔积作用的双方是两个集合,作用的结果是一个新的集合. A×B={(a,b)|a∈Aandb∈B} 现有两向量: >> p = [1, 5, 10]; & ...
- 局部QEventLoop帮助QWidget不消失(也就是有一个局部事件循环始终在运行,导致程序被卡住那里,但仍可以接受事件。说白了就是有一个while语句死活不肯退出,直到收到退出信号)
熟悉的陌生人 Qt 是事件驱动的,所以当你用Qt的时候,几乎时时刻刻和 QEventLoop 打交道.,只是你可能没有意识到: QCoreApplicaton::exec() QApplication ...
- C++ Primer Plus的若干收获--(十一)
本篇主要讲了转换函数的利与弊以及简要介绍流与iostream文件 因为在本篇仍然要使用上篇的Stonewt类,这里首先给出其代码 <span style="font-size:18px ...
- JS 实现Map
function Map() { this.arr = new Array(); var struct = function(key, value) { this.key = key; this.va ...
- VC中引用第三方库,常见的库冲突问题
Q:VC中引用第三方库,常见的库冲突问题 环境:[1]VS2008 [2]WinXP SP3 A1(方法一): [S1]第三方库(Binary形式的)如果同主程序冲突,则下载第三方库的源码[S2]保持 ...