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使用误区以及隐藏的内存破坏和内存泄漏的更多相关文章

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

    图片加载性能优化永远是Android领域中一个无法绕过的话题,经过数年的发展,涌现了很多成熟的图片加载开源库,比如Fresco.Picasso.UIL等等,使得图片加载不再是一个头疼的问题,并且大幅降 ...

  2. PHP Fileinfo组件越界内存破坏漏洞

    漏洞版本: PHP PHP 5.x 漏洞描述: BUGTRAQ ID: 66002 CVE(CAN) ID: CVE-2014-2270 PHP是一种HTML内嵌式的语言. PHP的file程序在解析 ...

  3. C++内存管理2-内存泄漏

    1 C++中动态内存分配引发问题的解决方案 假设我们要开发一个String类,它可以方便地处理字符串数据.我们可以在类中声明一个数组,考虑到有时候字符串极长,我们可以把数组大小设为200,但一般的情况 ...

  4. C++ Primer : 第十二章 : 动态内存之动态内存管理(new和delete)

    C++语言定义了两个运算符来分配和释放动态内存:运算符new分配内存,运算符delete释放new分配的内存. 运算符new和delete 使用new动态分配和初始化对象 在自由空间分配的内存是无名的 ...

  5. Java的内存管理与内存泄露

    作为Internet最流行的编程语言之一,Java现正非常流行.我们的网络应用程序就主要采用Java语言开发,大体上分为客户端.服务器和数据库三个层次.在进入测试过程中,我们发现有一个程序模块系统内存 ...

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

    当我们在用C++做底层驱动的时候,经常会遇到内存不足的警告,究其原因,往往是因为内存出现溢出,泄露或者越界等原因.那么他们之间有什么联系吗? 内存溢出(out of memory) 是指程序在申请内存 ...

  7. Linux内存描述之内存区域zone--Linux内存管理(三)

    1 内存管理域zone 为了支持NUMA模型,也即CPU对不同内存单元的访问时间可能不同,此时系统的物理内存被划分为几个节点(node), 一个node对应一个内存簇bank,即每个内存簇被认为是一个 ...

  8. [C]内存管理、内存泄露、堆栈

    原文地址:https://www.cnblogs.com/youthshouting/p/4280543.html,转载请注明源地址. 1.内存分配区间:         对于一个C语言程序而言,内存 ...

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

    1.内存分配区间:         对于一个C语言程序而言,内存空间主要由五个部分组成:代码段(.text).数据段(.data).静态区(.BSS).堆和栈组成.         BSS段:BSS段 ...

随机推荐

  1. RabbitMQ 延迟队列实现订单支付结果异步阶梯性通知

    在第三方支付中,例如支付宝.或者微信,对于订单请求,第三方支付系统采用的是消息同步返回.异步通知+主动补偿查询的补偿机制. 由于互联网通信的不可靠性,例如双方网络.服务器.应用等因素的影响,不管是同步 ...

  2. 经典卷积神经网络的学习(三)—— Inception Net

    Google Inception Net 首次出现在 ILSVRC 2014 的比赛中(和 VGGNet 同年),就以较大优势拔得头筹.那届比赛中的 Inception Net 一般被称为 Incep ...

  3. 生成式模型(generative) vs 判别式模型(discriminative)

    Andrew Ng, On Discriminative vs. Generative classifiers: A comparison of logistic regression and nai ...

  4. win10 uwp 使用 msbuild 命令行编译 UWP 程序

    原文:win10 uwp 使用 msbuild 命令行编译 UWP 程序 版权声明:博客已迁移到 http://lindexi.gitee.io 欢迎访问.如果当前博客图片看不到,请到 http:// ...

  5. vue: 关于多路由公用模板,导致组件内数组缓存问题

    当多个路由复用同一个模板,此时在这几个路由间切换,模板并不会重新挂载.针对这个情况,我们需要在当前逻辑内对路由做监听,在发生变化时更新对应属性,已满足需求. 但是,在实现的过程中会遇到如下情况: 如图 ...

  6. css 单选框 样式 填充自定义背景 after

    input[type='radio'] //width 16px //height 16px display none //input[type='radio']:chcked // backgoun ...

  7. struts2 no extension(excludePattern)

    采用struts2 小伙伴非常希望更改或删除action扩展,本文将帮助你实现 struts2-core-2.3.16.jar , 下载链接: http://repo1.maven.org/maven ...

  8. c语言学习笔记(7)——数组

    一.为什么需要数组1.为了解决大量同类型的数据存储和使用2.为了模拟现实世界二.数组的分类1.一维数组为n个变量连续分配存储空间所有的变量数据类型必须相同所有变量所占的字节大小必须相等初始化:完全初始 ...

  9. Android程序猿必掌握的sqlite数据库连表查询

    SQL查询的基本原理:两种情况介绍. 第一.   单表查询:依据WHERE条件过滤表中的记录,形成中间表(这个中间表对用户是不可见的):然后依据SELECT的选择列选择对应的列进行返回终于结果. 第二 ...

  10. Apache Cordova开发环境搭建(二)VS Code

    原文:Apache Cordova开发环境搭建(二)VS Code 版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/u011127019/articl ...