库博在线客服 结束对话

库博服务助手小白

正在为您服务……

联系我们

010-62766856

cobot@beidasoft.com

C++中Variant类内存泄漏分析报告

作者:孙永杰

Variant介绍

1:variant结构说明:

常用的variant数据类型有VARIANT和_variant_t两种

其中VARIANT是结构体,具体代码如下:(vs2017/win10默认库,OAIdl.hline466-539)

1. typedef /* [wire_marshal] */ struct tagVARIANT VARIANT;  

2.   

3. struct tagVARIANT  

4.     {  

5.     union   

6.         {  

7.         struct __tagVARIANT  

8.             {  

9.             VARTYPE vt;  

10.             WORD wReserved1;  

11.             WORD wReserved2;  

12.             WORD wReserved3;  

13.             union   

14.                 {  

15.                 LONGLONG llVal;  

16.                 LONG lVal;  

17.                 BYTE bVal;  

18.                 SHORT iVal;  

19.                 FLOAT fltVal;  

20.                 DOUBLE dblVal;  

21.                 VARIANT_BOOL boolVal;  

22.                 _VARIANT_BOOL bool;  

23.                 SCODE scode;  

24.                 CY cyVal;  

25.                 DATE date;  

26.                 BSTR bstrVal;  

27.                 IUnknown *punkVal;  

28.                 IDispatch *pdispVal;  

29.                 SAFEARRAY *parray;  

30.                 BYTE *pbVal;  

31.                 SHORT *piVal;  

32.                 LONG *plVal;  

33.                 LONGLONG *pllVal;  

34.                 FLOAT *pfltVal;  

35.                 DOUBLE *pdblVal;  

36.                 VARIANT_BOOL *pboolVal;  

37.                 _VARIANT_BOOL *pbool;  

38.                 SCODE *pscode;  

39.                 CY *pcyVal;  

40.                 DATE *pdate;  

41.                 BSTR *pbstrVal;  

42.                 IUnknown **ppunkVal;  

43.                 IDispatch **ppdispVal;  

44.                 SAFEARRAY **pparray;  

45.                 VARIANT *pvarVal;  

46.                 PVOID byref;  

47.                 CHAR cVal;  

48.                 USHORT uiVal;  

49.                 ULONG ulVal;  

50.                 ULONGLONG ullVal;  

51.                 INT intVal;  

52.                 UINT uintVal;  

53.                 DECIMAL *pdecVal;  

54.                 CHAR *pcVal;  

55.                 USHORT *puiVal;  

56.                 ULONG *pulVal;  

57.                 ULONGLONG *pullVal;  

58.                 INT *pintVal;  

59.                 UINT *puintVal;  

60.                 struct __tagBRECORD  

61.                     {  

62.                     PVOID pvRecord;  

63.                     IRecordInfo *pRecInfo;  

64.                     }   __VARIANT_NAME_4;  

65.                 }   __VARIANT_NAME_3;  

66.             }   __VARIANT_NAME_2;  

67.         DECIMAL decVal;  

68.         }   __VARIANT_NAME_1;  

69.     } ;  

70. typedef VARIANT *LPVARIANT;  

71.   

72. typedef VARIANT VARIANTARG;  

73.   

74. typedef VARIANT *LPVARIANTARG;  

_variant_t是封装了VARIANT的一个类,通过构造函数,重载等号等方式实现了对VARIANT的合理使用,析构时能自动释放内存,能够避免大部分内存泄漏情况。

(vs2017/win10默认库,comutil.hline1002-2324

image.png

image.png

2:VariantClear介绍

函数声明:

HRESULT WINAPI VariantClear

VARIANTARG * 

pVarg

image.png

传入VARIANTARG指针,首先根据成员变量vt确认数据类型,之后根据类型对其内部可能存在分配的资源调用相应的释放函数自动释放,对应表如下

数据类型

Vt类型

union成员变量名

释放函数

SAFEARRAY

VT_SAFEARRAY

parray

SafeArrayDestroy


BSTR

VT_BSTR

bstrVal

SysFreeString


__tagBRECORD

VT_RECORD

pRecInfo/pvRecord

IRecordInfo_RecordClear

IRecordInfo_Release

IUnknown

VT_UNKNOWN

punkVal

IUnknown_Release


IDispatch

VT_DISPATCH

pdispVal



 

可认为调用VariantClear之后,variant类型变量当前占用的内存均已正确释放。但不能预先修改成员变量vt

 

源码参考:

https://doxygen.reactos.org/df/d85/variant_8c.html#acb0574c35970c387a0949da25c3d1f2b

Variant类型独有的可能发生内存泄漏的情况

variant类型变量特殊在以下几点。

1:VARIANT类型:

与正常的结构体处理相似,区别在于当满足格式要求时(vt类型),系统实现了VariantClear方法统一释放内存。

 

2:_variant_t类型:

构造函数,析构函数,等号重载等会自动处理大部分内存问题。但使用错误的情况下也会引入部分新问题。

部分问题属于较高级的程序理解范畴,是代码逻辑问题,不在常规静态代码分析的检测范围,需要较复杂的专项定制。

 

以下设计一组循环相关的例子并加以说明。

 

 

内存泄漏情况:

image.png

第二次赋值时,tmp=L”123”语句等号被重载过,等价于:

image.png

image.png

又等价于image.png

其中SysAllocString会分配内存。对循环展开如下:

image.png

第1次循环第2次赋值中通过SysAllocString分配内存,赋给tmp.bstrVal,并设置vt为VT_BSTR

第2次循环第一次赋值中没有Clear便修改了vt,并对tmp.intVal赋值。根据VARIANT的数据结构,tmp.intVal与tmp.bstrVal在同一union下,共用一块内存。赋值之后之前分配的内存值被覆盖,变为野指针,导致内存泄漏。

由于已经变为野指针,第2次循环第二次赋值中的VariantClear并不能正确释放内存。

第100000次循环第二次赋值中分配的内存会被析构函数正确释放,该次没有内存泄漏。

 

该例循环中共计产生99999次内存泄漏。


正确情况对比1

image.png

内存泄漏的本质原因与声明在循环内外没有关系,但本例如果声明在循环内部不会发生内存泄漏。

类似上例,将循环展开后如下:

image.png

其中每次SysAllocString后均有相应的VariantClear操作,并且对应的vt类型均为VT_BSTR,可以正确释放指针。注意本例中如循环中第一次赋值和第二次赋值顺序颠倒,析构函数不会正确释放内存,仍会导致内存泄漏。

正确情况对比2:

image.png

代替

image.png

对_variant_t进行初始化,也不会导致内存泄漏。

image.png

image.png

语句同样被重载过,等价于:

image.png

同样对其循环展开如下:

image.png

同样每次通过SysAllocString分配的内存均被释放,不会导致内存泄漏

结论

经分析,与variant相关的问题至少还有4-5类

我们本次发版仅针对上文提到的循环相关的内存泄漏例子(如下图),按二级规则形式报出。

image.png