focus on linux, c/c++, lua

被delete难倒了

话说我有一个结构体如下:
struct stReplayData
{
    
int nDelay;        // 该数据在上一条消息之后的延迟,仿真(目前自定义1秒钟)
    char* pData;    // 网络数据包的内容
    int nLen;        // 长度

    stReplayData()
    
{
        nDelay 
= 0;
        nLen 
= 0;
        pData 
= NULL;
    }


    stReplayData(
int nLength)
    
{
        nDelay 
= 0;
        nLen 
= nLength;
        pData 
= NULL;
        
if (nLen > 0)
        
{
            pData 
= new char[nLen];
        }
                
    }


    stReplayData(
const stReplayData& src)
    
{
        
if (this == &src)
        
{
            
return;
        }

        
*this = src;
    }


    stReplayData
& operator = (const stReplayData& src)
    
{
        
if (this == &src)
        
{
            
return *this;
        }

        nDelay 
= src.nDelay;
        
if (pData != NULL)
        
{
            delete[] pData;
            pData 
= NULL;
        }
        
        nLen 
= src.nLen;
        
if (nLen > 0)
        
{
            pData 
= new char[nLen];        
            memcpy(pData, src.pData, nLen);
        }

        
return *this;
    }


    
~stReplayData()
    
{
        nDelay 
= 0;
        nLen 
= 0;
        
if (pData != NULL)
        
{
            delete[] pData;            
            pData 
= NULL;
        }
        
    }

}
;

我定义了一个vector<stReplayData*> m_vecReplay,然后new了一些stReplayData ,push_back这些指针进去,最后程序释放资源的时候,居然报调用释放指针出错了,报的错就是平时见的很多的Heap上指针无效的错误,基本上是说stReplayData的析构函数有问题,我了个擦,我怎么没看出哪里有问题呢?

=====================================更多的代码如下===============================================
我封装了一个dll作为一个公共模块,自然数据都会在这个公共模块中存储,其中内存数据的管理也会在这个dll中去做,也就是说,new和delete都会由这个dll自己去管理,
使用者只要去调接口,然后把需要存储的数据地址传给dll,让dll自己去拷贝即可。模块其实非常简单:
class CReplayManager : public IReplayManager
{
    typedef vector
<stReplayData*> VECREPLAY;
public:
    CReplayManager();
    
virtual ~CReplayManager();
    
virtual bool PushData(stReplayData* pData);
    
virtual stReplayData* PopData();
    
virtual stReplayData* GetTailData();
    
virtual void ClearData();
    
virtual void Release();    
private:
    VECREPLAY m_vecReplay;
}
;
这里还有个模版函数,让主程序朝dll写数据,
template<class M, class T>
void WriteReplay(M* m, const T& t)
{
    CWrite cw;
    cw.Write(t);
    stReplayData
* pReplay = new stReplayData(cw.GetLen());
    
if (pReplay != NULL)
    
{        
        pReplay
->nLen = cw.GetLen();
        memcpy(pReplay
->pData, cw.GetData(), cw.GetLen());
        m
->PushData(pReplay);
    }
        
}
另:在主程序里单独的操作stReplayData是没什么问题的,我也试验过。

=================================总结=================================
re: 被delete难倒了 2011-03-31 11:49 dizhu

问题就是SAFE_DELETE((*it)); 这个是在exe中new的,不能在dll中delete。

深入:
如果一个EXE调用一个DLL时,用new和delete分配和释放内存为什么应该放在同一个背景下的原因。得出的结论是,如果EXE和DLL有一个不是用动态链接CRT库(C   runtime   library)的方式使用CRT的话(Multi-threaded Debug DLL (/MDd)),或者是EXE和DLL动态链接的CRT库的版本不同时,EXE和DLL将会各自拥有各自的堆空间,所以在DLL中new的东西务必在DLL中delete。

posted on 2011-03-30 17:29 zuhd 阅读(2172) 评论(29)  编辑 收藏 引用 所属分类: c/c++

评论

# re: 被delete难倒了 2011-03-30 17:36 namelij

你去看看vector里面的对象是怎么释放的,就明白了  回复  更多评论   

# re: 被delete难倒了 2011-03-30 17:46 dizhu

贴下完整的代码  回复  更多评论   

# re: 被delete难倒了 2011-03-30 17:50 Kevin Lynx

@dizhu
问题确实可能出在其他地方。  回复  更多评论   

# re: 被delete难倒了 2011-03-30 17:55 千暮(zblc)

我按你说的做了一遍 没有报错 - -bnr 你咋不把调用的代码发下  回复  更多评论   

# re: 被delete难倒了[未登录] 2011-03-30 20:58 vincent

应该还是也指着啊  回复  更多评论   

# re: 被delete难倒了[未登录] 2011-03-30 20:59 vincent

野指针……  回复  更多评论   

# re: 被delete难倒了 2011-03-31 00:50 Mensch88

1. 就这段代码本身来说,有一个错误:拷贝构造函数 stReplayData(const stReplayData& src) 里的指针pData没有初始化!

2.stReplayData的析构函数本身没有问题。程序报错应该是调用了两次析构函数造成的。
比如说,很有可能在使用时会犯这样的错误:
stReplayData data(otherdata);
vec.push_back(&data);
由于data本身会调用析构函数delete pData,而delete vec里面的数据时也会调用data的析构函数,于是挂了。

  回复  更多评论   

# re: 被delete难倒了 2011-03-31 01:13 Mensch88

不好意思,第二点我说得不大对,因为楼主在析构函数里判断了 if (pData != NULL), 并且之后会把pData=NULL,所以调用两次析构在单线程环境里不会问题。当然多线程环境下就另说了。

但我所陈述的问题依然是存在的,只是这不是因为两次析构造成,而是因为data是栈上的数据,不允许delete,所以挂了。  回复  更多评论   

# re: 被delete难倒了 2011-03-31 09:14 zuhd

@Mensch88
1. 就这段代码本身来说,有一个错误:拷贝构造函数 stReplayData(const stReplayData& src) 里的指针pData没有初始化!

拷贝构造函数是调用operator =来着  回复  更多评论   

# re: 被delete难倒了 2011-03-31 09:36 dizhu

@Mensch88
vector里面保存的是指针,不会调用delete 的,不会存在你说的两次析构函数调用  回复  更多评论   

# re: 被delete难倒了 2011-03-31 09:39 dizhu

@dizhu
如果楼主
stReplayData data(otherdata);
vec.push_back(&data);
然后还去delete vec里面的数据,那就是楼主代码写的有问题,所以说还是贴下完整的代码,才能知道问题。但就这个stReplayData,还真看不出为什么会挂  回复  更多评论   

# re: 被delete难倒了 2011-03-31 10:13 zuhd

我更新了帖子 贴了更多的代码 想尝试的朋友 可以自己简单修改下即可  回复  更多评论   

# re: 被delete难倒了 2011-03-31 11:13 zuhd

问题我找到了,是我以前遇到的老问题
virtual bool PushData(stReplayData* pData);
这个接口设计有问题,dll的接口应该用标准的c++类型,我只知道其然,不知道所以然,了解详情的说下  回复  更多评论   

# re: 被delete难倒了 2011-03-31 11:17 dizhu

@zuhd
重点把delete stReplayData 的代码 和 CReplayManager 贴出来看下  回复  更多评论   

# re: 被delete难倒了 2011-03-31 11:22 zuhd

@dizhu
看了头文件基本就能猜到代码了吧 中规中矩的容器操作代码而已

另:我在gcc中的头文件大量的使用了自定义的类,貌似没发现过什么问题,怎么用vc上来就碰到这个,是巧合还是必然?肿么办?有没有,有没有?  回复  更多评论   

# re: 被delete难倒了 2011-03-31 11:24 dizhu

@zuhd
我比较关心CReplayManager 的Release 以及 ClearData 的实现。是不是在这里面delete stReplayData 了??  回复  更多评论   

# re: 被delete难倒了 2011-03-31 11:41 zuhd

void CReplayManager::ClearData()
{
VECREPLAY::iterator it = m_vecReplay.begin();
for (; it != m_vecReplay.end(); it++)
{
SAFE_DELETE((*it));
}
m_vecReplay.clear();
}  回复  更多评论   

# re: 被delete难倒了 2011-03-31 11:49 dizhu

@zuhd
void CReplayManager::ClearData()
{
VECREPLAY::iterator it = m_vecReplay.begin();
for (; it != m_vecReplay.end(); it++)
{
SAFE_DELETE((*it));
}
m_vecReplay.clear();
}
问题就是SAFE_DELETE((*it)); 这个是在exe中new的,不能在dll中delete。
原因:http://blog.csdn.net/blz_wowar/archive/2008/03/13/2176536.aspx  回复  更多评论   

# re: 被delete难倒了 2011-03-31 11:56 zuhd

@dizhu
在exe中new,不能在dll中delete的?
exe和dll用的是同一个堆栈空间的,  回复  更多评论   

# re: 被delete难倒了 2011-03-31 17:48 Mensch88

zuhd@zuhd
--拷贝构造函数是调用operator =来着

拷贝构造函数是调用了operator=,但是operator=里面会判断pData是否为NULL:
if (pData != NULL)
{
delete[] pData;
pData = NULL;
}
必须注意的是,这时pData还没有被初始化。这样就会执行 delete[] pData;
从而出错。  回复  更多评论   

# re: 被delete难倒了 2011-03-31 17:55 Mensch88

@dizhu
--然后还去delete vec里面的数据,那就是楼主代码写的有问题,所以说还是贴下完整的代码,才能知道问题。但就这个stReplayData,还真看不出为什么会挂

因为楼主说了vec里面是一些new出来的指针,而之后程序释放资源时报错,于是我估计楼主所说的程序释放资源就是指将vec里面的指针delete。
楼主后来的代码也证实了我的猜测。

  回复  更多评论   

# re: 被delete难倒了 2011-03-31 18:31 Mensch88

楼主的问题算是CRT的bug么?
Linux下测试,无论是静态还是动态链接都没发现这种问题。  回复  更多评论   

# re: 被delete难倒了 2011-03-31 23:54 flyinghearts


建议还是用 vector<char> 或 string 代替 char*

stReplayData 设计得有点问题,
一般都是 在拷贝构造函数中进行分配新内存处理, 然后在赋值函数中调用拷贝构造函数,构造一个临时类对像,与原类对象进行交换。

你的实现刚好相反,拷贝构造函数调用 赋值函数,但 赋值函数中用到的 nlen 和 pdata两个值都未初始化,UB行为。

实际上,这两个判断都是可以去掉的,new char[0] 是有意义的,没必要对 nlen进行判断
delete[] p 当p是空指针时,没有任何效果,因此没必要对pdata进行判断

拷贝构造函数中 对指针的判断 也是多余的。
另外,要先分配新内存,再释放旧内存,保证 异常安全。

  回复  更多评论   

# re: 被delete难倒了 2011-04-01 09:44 zuhd

@flyinghearts
那个拷贝构造函数确实有点问题,以前拷贝构造函数调用=写顺手了,没发现有内存操作的这么写有这个陷阱,改了一下:
stReplayData(const stReplayData& src)
{
if (this == &src)
{
return;
}
nDelay = src.nDelay;
nLen = src.nLen;
pData = new char[nLen];
if (pData != NULL)
{
memcpy(pData, src.pData, nLen);
}
}

stReplayData& operator = (const stReplayData& src)
{
if (this == &src)
{
return *this;
}
nDelay = src.nDelay;
nLen = src.nLen;
if (pData != NULL)
{
delete[] pData;
pData = NULL;
}
pData = new char[nLen];
if (pData != NULL)
{
memcpy(pData, src.pData, nLen);
}
return *this;
}

至于你说的:
另外,要先分配新内存,再释放旧内存,保证 异常安全。
好像我一直都是先delete 再new ,可能一直懒得用个临时的指针来保存pData吧,不过你这么说的道理是??  回复  更多评论   

# re: 被delete难倒了[未登录] 2011-04-01 11:32 hdqqq

如果楼主使用 /mt 编译,dll启动时使用自己的堆, 在主程序中new 出来的对象,再通过指针传给dll,然后在dll中释放,会产生错误,主程序和dll使用不同的堆。  回复  更多评论   

# re: 被delete难倒了[未登录] 2011-04-01 14:39 杨粼波

把struct改为class,然后把数据private,测试一次看看。排除客户代码应用上的错误。

当然了,也有可能在调用的时候,被其他地方的错误牵连导致这个struct的数据被损坏。

如果牵扯到DLL的话,那有可能是exe和dll两者之间的版本不一致所导致,DLL的导出地址是用的一个,却不想exe用了另外一个,所以发生错误,这是有可能发生的。用VS的编译器,特别是2003,你需要依照以下步骤重编译:清理全部(包括dll和exe)->重编。  回复  更多评论   

# re: 被delete难倒了[未登录] 2011-04-01 15:40 杨粼波

http://blog.csdn.net/ssli/archive/2009/06/16/4272484.aspx  回复  更多评论   

# re: 被delete难倒了 2011-04-01 23:30 flyinghearts

 
class stReplayData
{
  
int nDelay;     // 该数据在上一条消息之后的延迟,仿真(目前自定义1秒钟)
  size_t nLen;  // 长度
  char* pData;    // 网络数据包的内容
  
public:  
  stReplayData(size_t nLength 
= 0): nDelay(0), 
      nLen(nLength), pData(
new char[nLength]()) {}
  
  
~stReplayData() { delete[] pData; }
  
  stReplayData(
const stReplayData& src):  nDelay(src.nDelay),
      nLen(src.nLen), pData(
new char[src.nLen]) 
  {
    memcpy(pData, src.pData, nLen);  
  }
    
  stReplayData
& operator=(stReplayData tmp) //构建一个临时对像
  {
    nDelay 
= tmp.nDelay;
    nLen 
= tmp.nLen;
    std::swap(pData, tmp.pData);
    
return *this;
  }
};

 
可参考以前写的这篇文章:
http://blog.csdn.net/flyinghearts/archive/2010/06/16/5673089.aspx
  回复  更多评论   

# re: 被delete难倒了 2011-04-02 11:42 tingya

涉及到跨模块内存使用的问题,应该遵守谁使用,谁释放  回复  更多评论   


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