以前知道,在一個模塊中 new 一塊內存,然后在其它模塊中釋放,會導致異常。 但這次出現的問題比較古怪,剛開始根本沒想到是內存的原因。
查找原因比較曲折,但后來用虛函數的方法解決了問題,沒有修改代碼邏輯,感覺還是比較巧妙的^_^
執行環境簡單介紹如下:
一個靜態庫工程
MyShare.lib ,關鍵代碼如下:
// CMyObject 類部分代碼
class CMyObject
{
public:
CMyClient *m_pParent;
void FinalObject();
};
void CMyObject::FinalObject()
{
m_pParent->UnBand(this);
}
// CMyClient 類部分代碼
class CMyClient
{
std::map<CMyObject *,int> m_mapObjects;
public:
void Band(CMyObject *pObject);
void UnBand(CMyObject *pObject);
};
void CMyClient::Band(CMyObject *pObject)
{
m_mapObjects[pObject] = 1;
}
void CMyClient::UnBand(CMyObject *pObject)
{
m_mapObjects.erase(pObject); // 這里出現異常!
}
一個引用了 MyShare.lib 的 dll 工程:
MyCommon.dll,關鍵代碼如下
// 一個從 CMyObject 派生的類
class CMyCommonObject : public CMyObject
{

};
// 創建一個 CMyCommonObject
CMyObject * CreateObject()
{
return static_cast<CMyObject *>(new CMyCommonObject);
}
// 釋放對象
void ReleaseObject(CMyObject * pObject)
{
pObject->FinalObject();
delete pObject;
}
一個引用了以上兩個模塊 exe工程:
MyTest.exe,關鍵代碼如下:
int main(int argc, TCHAR* argv[])
{
CMyClient client;
// 調用 MyCommon.dll 中的代碼創建一個對象
CMyObject * pObject = CreateObject();
// 初始化對象
client.Band(pObject);
pObject->m_pParent = &client;

// 使用完畢,調用 MyCommon.dll 中的代碼釋放對象
ReleaseObject(pObject);
}
以上代碼,在運行時,會在 m_mapObjects.erase(pObject); 處出現異常;如果單純看類的每個函數,很難看出問題,另外,工程本身比較復雜,所以一直沒有懷疑是因為不同模塊之間分配和釋放內存導致的問題。
值得注意的是,這里的 MyTest.exe 和 MyCommon.dll 中包含了同一個靜態庫,也就是說,他們之中都有 CMyObject 和 CMyClient 的二進制代碼!很容易向導的是,問題應該出在 Band 和 UnBand 。毫無疑問,以上代碼中的 client.Band 執行的是
MyTest.exe 中的代碼。那么,m_pParent->UnBand 執行的是哪里的代碼呢?之前我想當然的以為,既然 m_pParent 指針都是從 MyTest.exe 中傳遞來的,那肯定是執行的 MyTest.exe 中的代碼。后來在VC中調試時偶然發現,執行UnBand 的代碼在 MyCommon.dll 中,才突然想到,調用類的成員函數不就相當于普通函數加一個 this 參數嗎?而普通函數編譯時就確定了地址,那肯定是指向自己模塊中的二進制代碼了。 那么,m_pParent->UnBand 肯定執行的 MyCommon.dll 中的代碼!這樣問題就真相大白了:在 MyTest.exe 中向 map 加入元素,而在 MyCommon.dll 中釋放,肯定會出錯!因為加入或刪除元素極可能造成堆內存分配的變化!
現在問題找到了,怎么解決呢?如果修改代碼邏輯,則會造成其它關聯代碼的修改,想起來都有些頭痛。問題主要是函數地址,什么函數是延遲綁定地址的呢?突然想到了虛函數!從 C++ 機制我們知道,調用虛函數其實是調用虛函數表中的函數指針,而虛函數表的內容是對象分配的時候填寫的!那么,這樣就能保證,無論在哪里調用虛函數,都是調用分配該對象的模塊中的代碼!
馬上將 CMyClient 中的 Band 和 UnBand 改成虛函數,再試,問題果然消失了,而且再次用 VC 調試,發現從 MyCommon.dll 調用 UnBand 時 ,也是在MyTest.exe 中執行 !^_^