C++ Builder 5 和 6中VCL的一个内存泄漏 BUG

xuting 2002-11-14 03:28:52
我在文档中心发了这篇,但是文章不再csdn首页,所以可能很多人不会注意到这个问题.

版本:C++Builder 5 、6
文件:{C++Builder Path}/include/vcl/utilcls.h
行号:1039 (implemention of operator AnsiString() const)
行号:1065 (implemention of operator WideString() const)
行号:1081 (implemention of operator wchar_t*() const)
  在上面三个操作符的实现里,变量v分配的资源(字符串)没有被释放,因为v是TBaseVariantT的变量,而TBaseVariantT只有constructor没有destructor,所以v在constructor中分配的内存就丢失了。

  写一个简单的实验程序就可以验证,但是可以举一个实际开发中的情形作为例子来检测:
  写一个包含事件的COM Server程序,并且事件接口函数中包含BSTR类型的参数,注册后,在C++ Builder中用import type library引入,并且生成包装类的component。然后建立一个简单的程序,把包装类的component放到程序的form上,并且实现component的事件函数(可以是空代码,但必须有这个函数)。比如:
void __fastcall MainForm::MyServerEvent1(BSTR msg)
{
// empty code
}
  C++ Builder在自动生成的包装类中,利用自动化接口调用每个事件函数,从源文件中可以看到调用MyServerEvent1的代码类似如下:
if( OnMyServerEvent1 )
OnMyServerEvent1( TVariant(params[0]) );

  在这个语句中隐式的调用了operator wchar_t*() const,因此内存泄漏发生了。让Server触发事件足够多的次数,内存泄漏就很明显的表现出来,可以一直耗光系统内存。
  解决办法很简单,稍微改动代码即可,别忘了先删除lib目录下的预编译头文件vcl60.csm(vcl50.csm),否则改动的代码不会重编译。
  虽然不确定,但是很可能需要重编译VCL的库,因为VCL中的代码中可能也有调用上面操作符的地方。
...全文
26 17 打赏 收藏 举报
写回复
17 条回复
切换为时间正序
当前发帖距今超过3年,不再开放新的回复
发表回复
jspxnet 2002-11-15
精彩
  • 打赏
  • 举报
回复
csz_cmy 2002-11-15
建议结帖加入FAQ.
  • 打赏
  • 举报
回复
xuting 2002-11-15
我的文章里的例子代码确实有错误,应该是:
void __fastcall MainForm::MyServerEvent1(BSTR msg)
{
::SysFreeString(msg);
}
要不也会有一次memory leak
  • 打赏
  • 举报
回复
xuting 2002-11-15
还有,函数的返回值里面有一个是:
E_OUTOFMEMORY Memory could not be allocated for the copy.
我想仅这一点你也应该看出函数是需要分配内存的,尽管不一定会。
  • 打赏
  • 举报
回复
xuting 2002-11-15
太失败了,你既然把MSDN的整个说明都贴出来,那说明你看了但是没有理解。请注意其中的一段话:
  “If pvargSrc is a VT_BSTR, a copy of the string is made. If pvargSrc is a VT_ARRAY, the entire array is copied. If pvargSrc is a VT_DISPATCH or VT_UNKNOWN, AddRef is called to increment the object's reference count.”
  我不知道你对这句话是怎么理解的,如果不分配内存的话,对于vt类型为BSTR的pvargSrc,难道::VariantCopy()过后,pvargDest和pvargSrc中的bstrVal指针指向同一块内存么?函数说明得很清楚,如果pvargSrc是VT_BSTR类型的VARIANT,那么其中的字符串会被拷贝一份。
  operator wchar_t*() const 应该改成
operator wchar_t*() const
{
TBaseVariantT v(*this);
v.ChangeType(VT_BSTR);
return V_BSTR(&v);
}
即使这样,调用者还必须显式的释放返回的指针。

  我希望你可以自己写一个程序检测一下到底有没有问题,事实可以证明。
  • 打赏
  • 举报
回复
xuting 2002-11-15
对阿,操作符实现中,定义变量v的代码是:
TBaseVariantT v(*this);

也就是说调用的构造函数不是TBaseVariantT(),
而是TBaseVariantT(const TBaseVariantT &src),
而在第二个构造函数里调用了::VariantCopy(this, (VARIANT*)&src);
分配资源就是在这里发生的
  • 打赏
  • 举报
回复
csz_cmy 2002-11-15
wchar_t系统确实分配了内存
正是因为这样说明BCB没有这个Bug
相反说明你的程序有Bug.
就象你使用new 而不delete就大闹BCB的内存泄漏一样.
我想你应该好好学C而不是C++Builder!
C++Builder入门容易精通难!!!!!
  • 打赏
  • 举报
回复
我来看看CB 2002-11-15
都很厉害。
佩服。
  • 打赏
  • 举报
回复
shadowstar 2002-11-15
问题不在这个类,而是你用的

// NOTE: Caller must free SysString
operator wchar_t*() const
{
TBaseVariantT v(*this);
v.ChangeType(VT_BSTR);
return ::SysAllocString(V_BSTR(&v));
}

这个实现是调用了 ::SysAllocString 分配资源,注意上面的注释,“// NOTE: Caller must free SysString”

..............
  • 打赏
  • 举报
回复
shadowstar 2002-11-15
faint, faint...

下面是MSDN中对这个函数的解释,自已看吧。。。

VariantCopy
Frees the destination variant and makes a copy of the source variant.

HRESULT VariantCopy(
VARIANTARG * pvargDest,
VARIANTARG * pvargSrc
);
Parameters
pvargDest
Pointer to the VARIANTARG to receive the copy.
pvargSrc
Pointer to the VARIANTARG to be copied.
Return Value
The return value obtained from the returned HRESULT is one of the following.

Value Meaning
S_OK Success.
DISP_E_ARRAYISLOCKED The variant contains an array that is locked.
DISP_E_BADVARTYPE The source and destination have an invalid variant type (usually uninitialized).
E_OUTOFMEMORY Memory could not be allocated for the copy.
E_INVALIDARG One of the arguments is invalid.

Comments
First, free any memory that is owned by pvargDest, such as VariantClear (pvargDest must point to a valid initialized variant, and not simply to an uninitialized memory location). Then pvargDest receives an exact copy of the contents of pvargSrc.

If pvargSrc is a VT_BSTR, a copy of the string is made. If pvargSrc is a VT_ARRAY, the entire array is copied. If pvargSrc is a VT_DISPATCH or VT_UNKNOWN, AddRef is called to increment the object's reference count.

If the variant to be copied is a COM object that is passed by reference, the vt field of the pvargSrc parameter is VT_DISPATCH | VT_BYREF or VT_UNKNOWN | VT_BYREF. In this case, VariantCopy does not increment the reference count on the referenced object. Because the variant being copied is a pointer to a reference to an object, VariantCopy has no way to determine if it is necessary to increment the reference count of the object. It is therefore the responsibility of the caller to call IUnknown::AddRef on the object or not, as appropriate.

Note The VariantCopy method is not threadsafe.

Requirements
Windows NT/2000: Requires Windows NT 3.1 or later.
Windows 95/98: Requires Windows 95 or later.
Header: Declared in oleauto.h.
Library: Use oleaut32.lib.

  • 打赏
  • 举报
回复
xuting 2002-11-15
faint,没法说了,不需要分配资源那要这个函数做什么,
干脆用memcpy好了
  • 打赏
  • 举报
回复
tjzzx888 2002-11-15
up
  • 打赏
  • 举报
回复
shadowstar 2002-11-15
这里只是拷贝,没有分配资源呀、
  • 打赏
  • 举报
回复
shadowstar 2002-11-14
utilcls.h
  • 打赏
  • 举报
回复
shadowstar 2002-11-14
下面是utilcls.hpp里的代码。
TBaseVariantT()
{
::VariantInit(this);
}

// ~TBaseVariant();
// don't do anything in the destructor: see TVariantT for a useful
// "by-value" VARIANT wrapper, and TVariantInParamT for the reason
// these classes exist in the first place.

// Copies
TBaseVariantT(const TBaseVariantT &src)
{
::VariantInit(this);
::VariantCopy(this, (VARIANT*)&src);
}

// TBaseVariantT& operator=(const TBaseVariantT &);
// Assignment is also deferred to the derived classes.
  • 打赏
  • 举报
回复
shadowstar 2002-11-14
TBaseVariantT()
{
::VariantInit(this);
}
这是TBaseVariantT的构造函数,只调用了VariantInit。

我们再到MSDN里看一下关于这个函数的解释:

VariantInit
Initializes a variant.

VOID VariantInit(
VARIANTARG * pvarg
);
Parameter
pvarg
Pointer to the VARIANTARG that will be initialized.
Comments
The VariantInit function initializes the VARIANTARG by setting the vt field to VT_EMPTY. Unlike VariantClear, this function does not interpret the current contents of the VARIANTARG. Use VariantInit to initialize new local variables of type VARIANTARG (or VARIANT).

这里并没有分配内存的操作,因此不必在析构函数里释放内存,所以也就不用显示的调用了。

你的实验我没做过,会不会是别的什么原因引起的呢?
  • 打赏
  • 举报
回复
wangxin_qaz 2002-11-14
安装了6之后,的确系统逐渐变得很慢,又是移动鼠标都很慢。
gz ......
  • 打赏
  • 举报
回复
发帖
VCL组件使用和开发

592

社区成员

C++ Builder VCL组件使用和开发
社区管理员
  • VCL组件使用和开发社区
加入社区
帖子事件
创建了帖子
2002-11-14 03:28
社区公告
暂无公告