返回   cpper编程论坛 > C/CPP/TMP/GP
注册账号 论坛帮助 会员列表 日历事件 搜索 今日新帖 标记版面已读

回复
 
LinkBack 主题工具 显示模式
  #1 (permalink)  
旧 2008-07-25
liuxinyu 的头像
高级会员
 
注册日期: 2006-02-09
帖子: 303
文章: 48
liuxinyu 正向着好的方向发展
默认 为什么会crash?

非常短的程序,我没有想明白原因
c++ 代码:
  1. #include <iostream>
  2.  
  3. class Base1{
  4. public:
  5.   virtual ~Base1(){}
  6. };
  7.  
  8. class Base2{
  9. public:
  10.   virtual void f()=0;
  11.   //virtual ~Base2(){}
  12. };
  13.  
  14. class Derive: public Base1, public Base2{
  15. public:
  16.   void f(){ std::cout<<"f\n"; }
  17.   ~Derive(){ std::cout<<"Derive dtor\n"; }
  18. };
  19.  
  20. void func(Base2* p){
  21.   p->f();
  22. }
  23.  
  24. int main(int, char**){
  25.   Derive* p = new Derive;
  26.   Base1* pb1=p;
  27.   Base2* pb2=p;
  28.   func(p);
  29.   //delete p;
  30.   //delete pb1;
  31.   delete pb2;
  32. }

我把Base2中的虚dtor干掉后,程序crash了,但是如果我把f一同干掉,又不crash
liuuuxin@WEIFANG ~/temp
$ g++ foo.cpp

liuuuxin@WEIFANG ~/temp
$ ./a
f
8 [sig] a 3292 _cygtls::handle_exceptions: Error while dumping state (prob
ably corrupted stack)
Segmentation fault (core dumped)

想不通道理了。
Digg this Post!Add Post to del.icio.usBookmark Post in TechnoratiFurl this Post!
回复时引用此帖
  #2 (permalink)  
旧 2008-07-26
polyrandom 的头像
超级版主
 
注册日期: 2002-09-03
帖子: 3,135
文章: 20
polyrandom 正向着好的方向发展
默认 回复: 为什么会crash?

其实所谓的delete分为两步,runtime在遇到一个delete的时候,理论上会调用一个叫做delete operator的东西,这个东西会先调用dtor,然后调用operator delete来释放内存。我们能够重载的是operator delete,而delete operator是无法重载的。
对于operator delete来说,它接受一个void*为参数,它只负责释放内存。那么,按照你的layout,pb1和d指向的内存地址肯定是不一样的,由于Base1有大小,pb2会有一个偏移。因此,对于operator delete来说,如果你传给它pb2,它就直接over了。
那么,delete operator怎么可以知道应该怎样传递正确地传递地址过去呢?这是因为,delete operator有点类似于virtual的。要实现这个virtual特性的先决条件就是,你的dtor必须是virtual的。这就可以解释为什么你把virtual注释了,就会over。
至于你把f也注释了,就不crash了。这个原因可能有多种多样的。也许仍然会crash,也许因为优化的原因,....。但是这个仍然是不对的,不能因为不crash就这样用。
Digg this Post!Add Post to del.icio.usBookmark Post in TechnoratiFurl this Post!
回复时引用此帖
  #3 (permalink)  
旧 2008-07-26
cat cat 当前离线
高级会员
 
注册日期: 2003-11-06
帖子: 1,560
文章: 6
cat 正向着好的方向发展
默认 回复: 为什么会crash?

compiler bug的感觉。
Digg this Post!Add Post to del.icio.usBookmark Post in TechnoratiFurl this Post!
回复时引用此帖
  #4 (permalink)  
旧 2008-07-26
初级会员
 
注册日期: 2007-12-05
帖子: 2
fxc123 正向着好的方向发展
默认 回复: 为什么会crash?

这个不就是Effective c++里面的条款7么?
Digg this Post!Add Post to del.icio.usBookmark Post in TechnoratiFurl this Post!
回复时引用此帖
  #5 (permalink)  
旧 2008-07-28
bankrock 的头像
高级会员
 
注册日期: 2003-12-11
帖子: 843
文章: 7
bankrock 正向着好的方向发展
默认 回复: 为什么会crash?

Effective里好像没讲过这种问题。我觉得只要有virtual function就可以了,不一定要vdtor编译器才会自动调整指针。虽然说基类写vdtor基本是不成文的规定
Digg this Post!Add Post to del.icio.usBookmark Post in TechnoratiFurl this Post!
回复时引用此帖
  #6 (permalink)  
旧 2008-07-28
liuxinyu 的头像
高级会员
 
注册日期: 2006-02-09
帖子: 303
文章: 48
liuxinyu 正向着好的方向发展
默认 回复: 为什么会crash?

我刚才在每个类里加了两个输出用于查看指针位置:
c++ 代码:
  1. static void* operator new(size_t size){
  2.     void* p=::operator new(size);
  3.     std::cout<<"Base2 new: "<<std::hex<<p<<"\n";
  4.     return p;
  5.   }
  6.  
  7.   static void operator delete(void* p){
  8.     std::cout<<"Base2 del: "<<std::hex<<p<<"\n";
  9.     ::operator delete(p);
  10.   }
程序输出:
Derived new: 0x6a02a8
f
Base2 del: 0x6a02ac
11946 [sig] a 3356 _cygtls::handle_exceptions: ...

所以pb1, pb2和p实际指向的位置是不同的。

而如果,调用delete pb1,就会显示:
Derived new: 0x6a02a8
f
Derive dtor
Derived del: 0x6a02a8

程序正确的进入了子类的operator delete,而不是进入了pb1的operator delete

有趣的是,这是个多重继承,如果把Base2的virtual dtor打开,并且delete pb2,则程序输出:
Derived new: 0x6a02a8
f
Derive dtor
Base2 dtor
Base1 dtor
Derived del: 0x6a02a8

注意到:第一父类Base1的dtor也被调用了。这里面vtable是什么样子呢?
Digg this Post!Add Post to del.icio.usBookmark Post in TechnoratiFurl this Post!
回复时引用此帖
  #7 (permalink)  
旧 2008-07-28
liuxinyu 的头像
高级会员
 
注册日期: 2006-02-09
帖子: 303
文章: 48
liuxinyu 正向着好的方向发展
默认 回复: 为什么会crash?

哦,顺便说一下最开始这个程序的应用背景。

本来我对单根继承下的virtual dtor和对应的vtable实现感觉自己理解还算清楚。不过看到了我们正在使用的框架规则,就感觉有怀疑了。

这个框架,对于所有堆上的对象,有一个公共基类CBase,方便内存管理。只要是从基类派生出来的子类,new出来后,只要放到到一个管理器内,就不用管了。

但是,有一个规则说:如果不是从这个基类派生出来的,那么如果new出来后,也放到管理器内,就会有麻烦。我觉得这个也能理解,因为这个管理器很可能内部维护了一个CBase*的指针容器,它在释放对象时,遍历容器,调用delete,例如:
c++ 代码:
  1. for(container<CBase*> it=coll.begin(); it!=coll.end(); ++it)
  2.   delete *it;

同时,我浏览框架代码,发现很多接口类,并没有定义virutal dtor,因此,如果是多继承,比如定义了一些接口Interface1, Interface2, 然后子类,继承CBase,实现Interface1和2,然后再把指向接口的指针放入管理器;那么就会像最前面的例子程序一样,crash。
Digg this Post!Add Post to del.icio.usBookmark Post in TechnoratiFurl this Post!
回复时引用此帖
  #8 (permalink)  
旧 2008-07-28
polyrandom 的头像
超级版主
 
注册日期: 2002-09-03
帖子: 3,135
文章: 20
polyrandom 正向着好的方向发展
默认 回复: 为什么会crash?

通常最好让每个会被派生的类带有virtual dtor。如果有些情况下做不到,那么,如果你有一个不带有virtual dtor的指针指向一个派生类对象,此时这个指针不应该表示任何形式的ownership。
另外,如果在一个类里面有虚函数,就应该把dtor也弄成virtual的,因为这样带来的开销太小了,而不加的话,带来的麻烦太大了。
Digg this Post!Add Post to del.icio.usBookmark Post in TechnoratiFurl this Post!
回复时引用此帖
  #9 (permalink)  
旧 2008-07-28
bankrock 的头像
高级会员
 
注册日期: 2003-12-11
帖子: 843
文章: 7
bankrock 正向着好的方向发展
默认 回复: 为什么会crash?

我觉得这句话有问题:
引用:
继承CBase,实现Interface1和2
这个“实现”是不是说派生类和Interface是is..a的关系,如果是的话就应该加上vdtor。另外一个存储CBase的容器怎么能存放指向Interface的指针呢?这和C++的强类型机制不符合
Digg this Post!Add Post to del.icio.usBookmark Post in TechnoratiFurl this Post!
回复时引用此帖
  #10 (permalink)  
旧 2008-07-28
liuxinyu 的头像
高级会员
 
注册日期: 2006-02-09
帖子: 303
文章: 48
liuxinyu 正向着好的方向发展
默认 回复: 为什么会crash?

引用:
作者: bankrock 查看帖子
另外一个存储CBase的容器怎么能存放指向Interface的指针呢?这和C++的强类型机制不符合
我看了下管理器的代码,有个重载的版本,接受void*指针的对象。释放时,仅仅free内存,而不调用dtor。如果希望释放时调用dtor,就需要通过模板把类型信息告诉管理器:
c++ 代码:
  1. class CleanupManager{
  2.   static void add(CBase* p);
  3.   static void add(void* p);
  4.   static void add(CleanupItem& x);
  5. };
  6. ...
  7.  
  8. template <class T>
  9. void addWithDelete(T* p){
  10.   CleanupManager::add(CleanupItem<T>(p));
  11. }
  12.  
  13. template <class T>
  14. void CleanupItem<T>::Delete(void* p){
  15.   delete static_cast<T*>(p);
  16. }

另外,我正在google这些问题:多继承时vtable是什么样子的?为什么对着Base2的指针delete,Base1的dtor也会得到调用?如果所有基类都有virutal dtor,那么就会按照继承树的关系一层一层调用到所有基类,届时各个兄弟基类dtor的调用顺序会是什么样子的?

此帖于 2008-07-28 07:42 PM 被 liuxinyu 编辑.
Digg this Post!Add Post to del.icio.usBookmark Post in TechnoratiFurl this Post!
回复时引用此帖
  #11 (permalink)  
旧 2008-07-28
liuxinyu 的头像
高级会员
 
注册日期: 2006-02-09
帖子: 303
文章: 48
liuxinyu 正向着好的方向发展
默认 回复: 为什么会crash?

查了一下,
多继承的情况下,对象在构造时,依照深度优先,从左向右遍历继承树进行构造。
在析构时,按照和构造正好相反的顺序[1]

vtable是这样的,子类的memory layout中,有n个直接父类的vtable,每个vtable中都有一个dtor的指针,[2]这样,任何一个父类声明了vdtor,都可以正确调用到子类的dtor。然后,按照上述规则沿着继承树析构,依次调用所有父类的dtor

最后一步,就是按照pora描述的,调用子类的operator delete释放内存。

--
[1]: [25] Inheritance -- multiple and virtual inheritance, C++ FAQ Lite
[2]: Virtual method table - Wikipedia, the free encyclopedia

此帖于 2008-07-28 08:23 PM 被 liuxinyu 编辑.
Digg this Post!Add Post to del.icio.usBookmark Post in TechnoratiFurl this Post!
回复时引用此帖
  #12 (permalink)  
旧 2008-07-29
bankrock 的头像
高级会员
 
注册日期: 2003-12-11
帖子: 843
文章: 7
bankrock 正向着好的方向发展
默认 回复: 为什么会crash?

virtual dtor语义上需要实现的就是:销毁基类指针时会调用实际所指向对象的dtor,之后的析构过程和普通对象的析构过程相同,这些C++ Primer或者TCPL上都有将的。我个人觉得最好不要管vtbl是怎么运作的,这些都应该被隐藏在编译器实现里,程序员只要管好语义正确就可以。
另外CleanupManager的清理方式有些奇怪,照理Interface不过提供了一些使用的接口函数,为什么要作为一个CleanItem存储到CleanupManager,难道这些结构提供的都是销毁功能的接口?
Digg this Post!Add Post to del.icio.usBookmark Post in TechnoratiFurl this Post!
回复时引用此帖
  #13 (permalink)  
旧 2008-09-05
初级会员
 
注册日期: 2004-10-21
帖子: 7
allnickname 正向着好的方向发展
默认 回复: 为什么会crash?

好像编译器被欺骗了。
以前遇到过这种情况:
把一个类指针强制成void指针后传给一个函数(函数只接受void指针,BS一下作者),然后函数内部delete这个指针,析构函数根本没被调用。这个对象的内存倒是释放了,但是这个对象指向的其他对象内存根本没被释放。
Digg this Post!Add Post to del.icio.usBookmark Post in TechnoratiFurl this Post!
回复时引用此帖
  #14 (permalink)  
旧 2008-09-06
polyrandom 的头像
超级版主
 
注册日期: 2002-09-03
帖子: 3,135
文章: 20
polyrandom 正向着好的方向发展
默认 回复: 为什么会crash?

引用:
作者: allnickname 查看帖子
好像编译器被欺骗了。
以前遇到过这种情况:
把一个类指针强制成void指针后传给一个函数(函数只接受void指针,BS一下作者),然后函数内部delete这个指针,析构函数根本没被调用。这个对象的内存倒是释放了,但是这个对象指向的其他对象内存根本没被释放。
这个只能怪写函数的人不好,或者说,这个作者希望传入的是一个POD。
Digg this Post!Add Post to del.icio.usBookmark Post in TechnoratiFurl this Post!
回复时引用此帖
回复

书签

主题工具
显示模式

发帖规则
不可以发表新主题
不可以发表回复
不可以上传附件
不可以编辑自己的帖子

启用 BB 代码
论坛启用 表情符号
论坛启用 [IMG] 代码
论坛禁用 HTML 代码
Trackbacks are 启用
Pingbacks are 启用
Refbacks are 启用



所有时间均为格林尼治时间 +9。现在的时间是 07:36 AM


Powered by vBulletin® 版本 3.7.0
版权所有 ©2000 - 2008,Jelsoft Enterprises Ltd.
(C) Copy Right All Right Reserved 2001 - 2007

Search Engine Friendly URLs by vBSEO 3.1.0