zoukankan      html  css  js  c++  java
  • 释放参数BSTR使用误区以及隐藏的内存破坏和内存泄漏

    PS:明天上午,非常郁闷,有很多简单基础的问题搞得我有些迷茫,哎,代码几天不写就忘。目前又不当COO,还是得用心记代码哦!

        BSTR应用误区以及隐藏的内存损坏和内存泄漏

        

        作者:magictong

        

        简介

        BSTR的数据结构是什么模样并非本文讨论的问题,但是却是本文的基础。在处理COM的跨平台编程的问题,需要定义一种通用的字符串类型,它就这样被发明白,而且它的结构很轻易匹配到不同的编程环境中,对于C++程序员来讲,要记着的最基本的一点就是分配BSTR结构时,并非简单的调用newmalloc就能够实现的,而且大部分的字符串相关的APIC库函数也是不能用于处理BSTR的,其实这也是应用BSTR的误区之一,在C++里面,BSTR被简单的definewchar_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] */BSTRpbstrTitle)

        {

        // 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] */BSTRpbstrText)

        {

        ::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_bstrTextCSomeClass的成员变量

        m_bstrText = bs// 错误做法

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

        }

        

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

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

        void CSomeClass::GetText(BSTRbs)

        {

        // m_bstrTextCSomeClass的成员变量

        bs = m_bstrText// 错误做法

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

        }

        

        BSTR的封装类CComBSTR

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

        1、被调用者违背out参数应用约定

        void GetText(/*[out]*/BSTRbstrText)

        {

        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]*/BSTRbstrText)

        {

        // ……

        }

        // use GetText

        CComBSTR bstrText(L"qq");

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

        // bstrText.Empty();

        GetText(bstrText);

        

        3、看一个费解一点的

        void GetText(/*[out]*/BSTRbstrText)

        {

        // ……

        }

        // 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

        

    文章结束给大家分享下程序员的一些笑话语录: 现在社会太数字化了,所以最好是有一个集很多功能于一身的设备!

    --------------------------------- 原创文章 By
    释放和参数
    ---------------------------------

  • 相关阅读:
    Synchronized 锁 批量重偏向 和批量撤销
    Synchronize 偏向锁,轻量级锁升级和撤销过程,对象头的变化
    JAVA 对象到底有什么
    什么是操作系统虚拟地址
    从C角度看 i = i+ 1本质
    Linux操作系统的三种锁机制
    SpringCloud启动过程中如何完成Nacos服务注册
    Nacos源码一
    JAVA线程的本质
    SpringCloud启动-Nacos服务注册
  • 原文地址:https://www.cnblogs.com/xinyuyuanm/p/3108990.html
Copyright © 2011-2022 走看看