2017-09-14 78 views
0

在我正在研究的這個程序中存在內存泄漏,就提交而言,它已經存在很長一段時間了。根據這兩個解釋explanation 1explanation 2每當使用=賦值運算符_bstr_t會導致內存泄漏。使用_bstr_t進行內存泄漏,無法修復

上下文 - 有一個數據庫對象,通常用於執行數據庫的快速sql查詢。每種方法最終使用以下方法

NvStatus DbUtils::ReadFromDatabase(IUnknown * poNvData, 
            const std::wstring & oConnectString, 
            const std::wstring & oSQLStatement) 
{ 
    //some checks 
    _bstr_t tbtSQLStr = oSQLStatement.c_str();//memory leak 
    _bstr_t tbtConnStr = oConnectString.c_str();//memory leak 

    //pass the _bstr_t to another method and get data from DB 
    return status; 
} 

根據文章這個方法會在每次它被稱爲查詢,因爲_bstr_t的數據庫以及如何創建時間的泄露數據。我的問題是我能做些什麼來防止程序炸燬並強制對_bstr_t對象進行垃圾回收?

Microsoft指出我使用它清理內存是我的責任,所以如何在不破壞數據傳遞給我的情況下做到這一點?我嘗試做一個字符串的深層副本,但失敗了...任何建議將不勝感激!

經過進一步調查我的內存泄漏的兩個熱點就是我第一次公佈這一個,希望這有助於

static bool GetValueFromVariant(VARIANT & tvInputValue, 
    std::wstring & roOutputValue) 
{ 
    _bstr_t tTemp = tvInputValue.bstrVal; 
    if(tTemp.length()>0) 
    { 
     roOutputValue = (wchar_t*) tTemp; 
    } 
    return true; 
} 

意見建議,這些_bstr_t應自動清洗自己了......然而,當調試我的Windows服務的堆大小,堆大小不斷增加,調試器繼續指向所有使用這些_bstr_t對象的函數。顯然這些_bstr_t沒有被清理。更多的上下文,這個內存泄漏的大部分源於反覆創建一個COM對象,但是當我完成它並且我檢查Release()函數調用返回的引用計數時我釋放該對象,它返回0.因此,我知道我沒有建立COM對象...

當把一個wstring指向_bstr_t的地址時會出現問題嗎?

+0

'_bstr_t'是本地'BSTR'的智能包裝,負責內存分配和釋放。因此你不應該在'_bstr_t'上調用'SysFreeString',因爲它的析構函數會處理這個問題。 – Aurora

+0

那麼如果內存泄露發生,如果它們在完成時應該記住它們的內存呢?我已經研究了vs2015調試器,並且內存增加源於使用這些_bstr_t對象的所有內容...爲什麼它們不被清理?有沒有辦法強制垃圾收集他們? @Aurora –

+0

我想我的問題歸結爲 - 是否有使用bstr而無需調用新的? @Aurora –

回答

0

您的示例的第一行不泄漏內存,因爲您將一個C樣式的字符串分配給_bstr_t包裝器。如果您將之前分配的BSTR分配給_bstr_t,情況會有所不同。這是你的第二個解釋中描述的問題。

考慮以下情況:

void foo() 
{  
    BSTR s1 = SysAllocString(L"String1"); 
    _bstr_t s2 = s1; 
} 

在這裏,本機BSTR使用SysAllocString分配並置於S1。下一行從s1構建新的BSTR,它位於s2中。當s2超出範圍時,其析構函數調用SysFreeString,從而取消分配副本。然而,原始的s1變量保持完整並且會泄漏。

爲了解決這個問題,你需要讓S2採取S1的所有權:

void foo() 
{ 
    BSTR s1 = SysAllocString(L"String1"); 
    _bstr_t s2(s1, false); 
} 

void foo() 
{ 
    BSTR s1 = SysAllocString(L"String1");   
    _bstr_t s2; 
    s2.Attach(s1); 
} 

正如我的意見指出,_bstr_t的析構函數會調用SysFreeString,從而負責釋放資源。

由於BSTR通常被緩存,所以您不可以立即目擊內存釋放。通過setting an environment variable,可以禁用此行爲用於調試目的。

+0

像你說的那樣,它應該釋放用於這些字符串的內存,但它永遠不會......這個程序將繼續泄漏內存,直到所有可用內存被系統用完。該程序再一次是Windows服務,不知道這是否有所作爲。根據vs2015調試器,我還添加了另一部分泄漏代碼。 –

+0

另一個想法:'_bstr_t'被引用計數。你可能將'_bstr_t'通過值傳遞給一個方法或一個類的實例,它被存儲在哪裏?這可能是一個可能的解釋,爲什麼Dtor沒有被觸發,因爲ref-count還沒有達到0。 – Aurora

+0

所以這一切都是因爲一個COM對象被創建並創建時,它是否已經存在或未被重新初始化,並且重新初始化的步驟會導致對上述方法的調用。也許對象的大小正在增加,但根據vs2015調試器,所有變量的大小都是相同的,但堆空間會增加每個快照。 –

0

在第一種情況下,根本不要撥打SysFreeString。沒有內存泄漏。

構造用於:

_bstr_t tbtSQLStr = oSQLStatement.c_str(); 

創建源字符串的一個副本,這個副本是由析構函數調用時tbtSQLStr超出範圍釋放。使用類類型包裝的重點在於,您不需要手動調用SysFreeString

請注意,根據// ....塊中代碼的確切性質,您可能甚至不需要創建該字符串的副本。


在第二種情況下(這是真的到了第一種情況下一個單獨的問題,您應該已經張貼2個不同的問題),也沒有內存泄漏。

tTemp分配副本; roOutputValue複製副本,然後tTemp的析構函數釋放第一個副本。

但是您可以通過創建和銷燬tTemp浪費的時間,你可以只寫:

if (SysStringLen(tvInputValue.bstrVal) > 0) 
    roOutputValue = tvInputValue.bstrVal; 

在那裏我假設了「真正的代碼」其實檢查變種保持在這一點上BSTR。

+0

是的,sysalloc和sysfree對於BSTR是有意義的,而_bstr_t是爲我做的這個包裝。其次,底部的代碼是逐字代碼的項目代碼。最後,當我將一個遠程調試器附加到我的代碼中時,唯一顯示堆棧視圖最大大小的兩個點源自ReadFromDatabase和GetValueFromVariant ...變量的所有大小保持完全相同,但內存增加.. –

+0

此外,當我瀏覽堆棧視圖時,我使用下拉菜單查看堆空間大小增加源於_bstr_t,它是_bstr_t :: _ bstr_t - > _bstr_t :: Data_t :: Data_t,因此堆大小增加是因爲這些_bstr_t的我可以在調試器中清楚地看到它 –

+0

@RAZ_Muh_Taz好吧,你應該添加代碼來檢查'VT_BSTR'是變體類型(我認爲有一個宏可以這樣做),然後才能真正使用bstr –