兔子的技术博客

兔子

   :: 首页 :: 联系 :: 聚合  :: 管理
  202 Posts :: 0 Stories :: 43 Comments :: 0 Trackbacks

留言簿(10)

最新评论

阅读排行榜

评论排行榜

以前知道,在一个模块中 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 中执行 !^_^
posted on 2009-09-22 14:29 会飞的兔子 阅读(446) 评论(2)  编辑 收藏 引用 所属分类: C++及开发环境

Feedback

# re: 不同模块中释放内存出错 2009-09-23 10:55 岳...
1. 搞的太复杂了,lib/dll/exe,来回折腾,要共享代码,要简单清晰,只用静态lib就行,把大量公共代码重用,这是有多个模块时最好;
2. dll的使用最好是私密的,不想共享代码的;这里就会出现模块分配内存的问题;
3. 在封装dll时一定注意2条:
1. 输出纯虚接口,这样容易思考,直接隔离;
2. dll内部的内存分配与释放,都在它自己内部完成;
4. C++有很多种方式和技巧,一定要只选一种最简单最适合自己的思考方式,不要混用,要不然根本没时间和精力思考应用逻辑,大量时间消耗在C++本身的技巧上,得不尝失;

呵呵。。。。。。。。。
  回复  更多评论
  

# re: 不同模块中释放内存出错 2009-09-23 11:54 会飞的兔子
回复老岳:
很好,有一定道理;
1条:现在系统必须这样,又要 lib (有些公共代码),又要 dll(COM组件)
2、3条:同意!
4条:部分同意。我们不能为用技巧而用技巧;当程序需要技巧的时候就用技巧,毕竟‘工欲善其事,必先利其器’嘛,^_^  回复  更多评论
  


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理