以前知道,在一個(gè)模塊中 new 一塊內(nèi)存,然后在其它模塊中釋放,會導(dǎo)致異常。 但這次出現(xiàn)的問題比較古怪,剛開始根本沒想到是內(nèi)存的原因。
查找原因比較曲折,但后來用虛函數(shù)的方法解決了問題,沒有修改代碼邏輯,感覺還是比較巧妙的^_^
執(zhí)行環(huán)境簡單介紹如下:
一個(gè)靜態(tài)庫工程
MyShare.lib ,關(guān)鍵代碼如下:
// 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); // 這里出現(xiàn)異常!
}
一個(gè)引用了 MyShare.lib 的 dll 工程:
MyCommon.dll,關(guān)鍵代碼如下
// 一個(gè)從 CMyObject 派生的類
class CMyCommonObject : public CMyObject
{

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

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