none
关于VC8多继承的一个问题,是否是为实现多继承对象布局的bug? RRS feed

  • 问题

  • struct _base1{};

    struct _base2{};

    struct _inheritance : _base1, _base2{};

    int main()
    {
        // Ok no problem
        _base1* p1 = new _inheritance;
        delete p1;

        // p2 now point to _base2 subobject, and the delete operator will cause error
        _base2* p2 = new _inheritance;
        delete p2;

        return 0;
    }

    由于VC8实现多继承的机制,第二种情况下,p2的值会为了指向_base2类型的子对象,增加了1个byte,原因是_base2在继承列表中排列顺序在后,这也是多继承的一种实现机制,Insight C++ Object Module中也早已说明。可以通过使用
    /d1reportAllClassLayout编译选项观察到这一点,如下:
    1>class _base1 size(1):
    1> +---
    1> +---
    1>class _base2 size(1):
    1> +---
    1> +---
    1>class _inheritance size(1):
    1> +---
    1> | +--- (base class _base1)
    1> | +---
    1> | +--- (base class _base2)
    1> | +---
    1> +---

    也可以通过反汇编看到:
    ; this is the first case, p1 point to subobject _base1 which is just the address of _inheritance and no problem
    00401000  push        1   
    00401002  call        operator new (401048h)
    00401007  push        eax 
    00401008  call        operator delete (40104Eh)

    ; this is the 2nd case, p2 point to subobject _base2 and ...
    0040100D  push        1   
    0040100F  call        operator new (401048h)
    00401014  add         esp,0Ch
    00401017  test        eax,eax
    00401019  je          wmain+2Ah (40102Ah)
    0040101B  add         eax,1                            ; here, for multi inheritance, the point add an offset of 1 to subobject _base2
    0040101E  push        eax 
    0040101F  call        operator delete (40104Eh) ; and, the call to operator delete will cause error, the free failed.

    因为观察到以上现象,很容易观察到是按照继承列表顺序,所以,试验了下面的情况

    struct _inheritance : _base1, _base2, _base3 {};
         
    _base3* p3 = new _inheritance;
    delete p3;


    果然也是这样。

    多继承一直是C++实现的一个问题,所以,我猜测其他编译器也许有类似的或者其他问题,于是对GCC进行了测试,但是,GCC在这一点上没有问题。



    2008年12月6日 2:39

答案

  • 实际上,您的代码是有问题的。如果您希望析构函数能够正确的得到调用。请给予最顶级的类virtual的析构函数:

     

    Code Snippet

    struct _base1

    {

    virtual ~_base1(){ }

    };

    struct _base2

    {

    virtual ~_base2(){ }

    };

    struct _inheritance : _base1, _base2{};

    int main()
    {
        // Ok no problem
        _base1* p1 = new _inheritance;
        delete p1;

        // p2 now point to _base2 subobject, and the delete operator is ok

        _base2* p2 = new _inheritance;
        delete p2;

        return 0;
    }

     

     

    实际上对于GCC在Diamond多重继承的状态下也会有这个问题。因为对于第二个基类,将没有恰当的vptr指引析构的方向。从而产生了这个问题。这是一个Undefined Behavior

     

    Code Snippet

     #include <iostream>
     #include <string>
     
     using namespace std;

     class GrandParent {
     public:
      // Adding a virtual destructor to the grandparent on the right side
      // fixes the problem
      //virtual ~GrandParent() {}

      // potential workaround:
      // adding a pure virtual function to handle deleting the object fixes the problem
      virtual void deleteMe()=0;
     };

     class LeftParent: public GrandParent {
     public:
      // a virtual destructor in the left parent doesn't help
      //virtual ~LeftParent() {}
     };

     class RightParent : public GrandParent {
     public:
      // a virtual destructor on the right parent also fixes the problem
      //virtual ~RightParent() {}
     };

     class Child: public LeftParent, public RightParent {
     public:
      void deleteMe() {
       delete this;
      }
     };
                    

     int main(int argc, char **argv) {
     
      // Declaring object to be a pointer to RightParent will cause a
      // segfault when it is deleted.  Declaring it a pointer to LeftParent
      // or Child produces the desired (non-segfaulting) behavior.
      RightParent *object = new Child();
      //LeftParent *object = new Child();
      //Child *object = new Child();
                  
      //cout << __FILE__ << ": trying to delete object " << object << ": " << endl;
     
      // comment out the 'delete object' line and uncomment this next line
      // to try the virtual function workaround.  It works, but is clunky.
      //object->deleteMe();
     
      delete object;
     
      //cout << __FILE__ << ": success! " << endl;
                             
      return 0;
     }

     

     

    2008年12月6日 5:06

全部回复

  • 实际上,您的代码是有问题的。如果您希望析构函数能够正确的得到调用。请给予最顶级的类virtual的析构函数:

     

    Code Snippet

    struct _base1

    {

    virtual ~_base1(){ }

    };

    struct _base2

    {

    virtual ~_base2(){ }

    };

    struct _inheritance : _base1, _base2{};

    int main()
    {
        // Ok no problem
        _base1* p1 = new _inheritance;
        delete p1;

        // p2 now point to _base2 subobject, and the delete operator is ok

        _base2* p2 = new _inheritance;
        delete p2;

        return 0;
    }

     

     

    实际上对于GCC在Diamond多重继承的状态下也会有这个问题。因为对于第二个基类,将没有恰当的vptr指引析构的方向。从而产生了这个问题。这是一个Undefined Behavior

     

    Code Snippet

     #include <iostream>
     #include <string>
     
     using namespace std;

     class GrandParent {
     public:
      // Adding a virtual destructor to the grandparent on the right side
      // fixes the problem
      //virtual ~GrandParent() {}

      // potential workaround:
      // adding a pure virtual function to handle deleting the object fixes the problem
      virtual void deleteMe()=0;
     };

     class LeftParent: public GrandParent {
     public:
      // a virtual destructor in the left parent doesn't help
      //virtual ~LeftParent() {}
     };

     class RightParent : public GrandParent {
     public:
      // a virtual destructor on the right parent also fixes the problem
      //virtual ~RightParent() {}
     };

     class Child: public LeftParent, public RightParent {
     public:
      void deleteMe() {
       delete this;
      }
     };
                    

     int main(int argc, char **argv) {
     
      // Declaring object to be a pointer to RightParent will cause a
      // segfault when it is deleted.  Declaring it a pointer to LeftParent
      // or Child produces the desired (non-segfaulting) behavior.
      RightParent *object = new Child();
      //LeftParent *object = new Child();
      //Child *object = new Child();
                  
      //cout << __FILE__ << ": trying to delete object " << object << ": " << endl;
     
      // comment out the 'delete object' line and uncomment this next line
      // to try the virtual function workaround.  It works, but is clunky.
      //object->deleteMe();
     
      delete object;
     
      //cout << __FILE__ << ": success! " << endl;
                             
      return 0;
     }

     

     

    2008年12月6日 5:06
  • thx
    查看了一下EC++的item07,确实如此。谢谢!
    2008年12月6日 6:04
  • 多重继承时,基类在内存中的排布未必和声明顺序相同,编译器可以自由选择顺序。VC会进行这样的优化以避免多余的this指针调整。

    2008年12月6日 7:16
  • 这个还是第一次听说。请问您能够给出一些例子以证明这一点吗?实际上这对于某些情况非常重要。

    2008年12月8日 5:58
  •  lxconan 写:

    这个还是第一次听说。请问您能够给出一些例子以证明这一点吗?实际上这对于某些情况非常重要。

     

    这点并不重要,因为标准并没有对此做任何规定。不要依赖于编译器的特定行为。

     

    Code Snippet

    #include <iostream>
    using namespace std;

    class A
    {
    public:
     //virtual void f() {}
     int a;
    };
    class B
    {
    public:
     virtual void f() {}
     int b;
    };

    class C:public A,public B
    {
     virtual void f() {}
    };

    int main()
    {
     C c;
     cout<<static_cast<A*>(&c)<<endl;
     cout<<static_cast<B*>(&c)<<endl;
    }

     


    B在A的前面,如果你把A里的注释去掉,A就会在B的前面

    2008年12月9日 5:53