C++オブジェクトメモリレイアウト:シングル継承、マルチ継承、ダミー継承

19844 ワード

0.はじめに
本明細書では、visual studio 2013によって作成されたクラスインスタンスオブジェクトのメモリ分布について説明する.理解したい場合は、メモリ分布のアドレスをよく見てください.
1.簡単な例
class A{
    int  a=1;
    char b=2;//    
    char c=3;//    
};
A instance;

1.1メモリの分散
アドレス
変数名

0x0093f9a0
instance
-
0x0093f9a0
a
0x00000001
0x0093f9a4
b,c
0xcccc0302(-859045118)
1.2説明
instanceはaのアドレスと同じです
1.3テストコード

int  *sp = (int *)&instance;
cout << *(sp++) << endl;
cout << *(sp) << endl;

  :
1 
-859045118

2.単一継承
class Base{

    int a=1;
    char b=2;
    char c = 3;
    virtual void f1(){
        cout << "Base::f1()" << endl;
    }
};
class Derived : private Base{
    int  d=4;
     void f1(){
         cout << "Derived::f1()" << endl;
    }
     virtual void f2(){
         cout << "Derived::f2()" << endl;
     }
     void f3(){
         cout << "Derived::f3()" << endl;
     }
};

Derived  d;

2.1メモリの分散
アドレス
変数名

0x00b8f7c0
d
-
0x00b8f7c0
_vfptr虚関数テーブルアドレス
-
0x00b8f7c4
Base::a
0x00000001
0x00b8f7c8
Base::b,Base::c
0xcccc0302(-859045118)
0x00b8f7cc
Derived::d
0x00000001
2.2説明
  • dと_vfptrのアドレスは同じで、説明対象の開始位置格納は虚函数表アドレス
  • である.
  • プライベート継承でもプライベート変数でも2.3のテストコードで
  • に直接アクセスできます.
  • 単一継承は1つの虚関数テーブルのみであり、虚関数はテーブルの
  • に順次配置される.
    2.3テストコード
        int  *dp = (int *)&d;
        //       
        typedef void(*fptr)(void);
        //f1
        int vfptr_address = *dp;//      
        int f1_address = ((int *)vfptr_address)[0];//            
        fptr f1p = (fptr)f1_address;//  f  
        f1p();//    f,   "Derived::f1()"               
        //f2
        int f2_address = ((int *)vfptr_address)[1];//            
        fptr f2p = (fptr)f2_address;//  f  
        f2p();//    f,   "Derived::f2()" ,
    
        //fptr fp = (fptr)(((int*)(*sp))[0]);//  
    
        cout << hex << dp <<","<< hex << *(dp) << endl;//vfptr       ,
        dp++;
        cout << hex << dp << "," << hex << *(dp) << endl;//a
        dp++;
        cout << hex << dp << "," << hex << *(dp) << endl;//b,c
        dp++;
        cout << hex << dp << "," << hex << *(dp) << endl;//d

    3.マルチ継承
    class Base1{
    
        int a=1;
        char b=2;
        char c = 3;
        virtual void f1(){
            cout << "Base1::f1()" << endl;
        }
        virtual void f2(){
            cout << "Base1::f2()" << endl;
        }
    };
    class Base2{
    
        int a = 1;
        char b = 2;
        char c = 3;
        virtual void f1(){
            cout << "Base2::f1()" << endl;
        }
        virtual void f2(){
            cout << "Base2::f2()" << endl;
        }
    };
    class Derived : private Base1, Base2{
        int  d=4;
         void f1(){
             cout << "Derived::f1()" << endl;
        }
    
         void f3(){
             cout << "Derived::f3()" << endl;
         }
    };
    
    Derived  d;
    //     Base1
    int* b1p = (int *)(Base1*)&d;
    //     Base2
    int* b2p = (int *)(Base2*)&d;

    3.1メモリ分布
    アドレス
    変数名

    0x0116f8b8
    &d,b1p , Base1::_vfptr虚関数テーブルアドレス
    -
    0x0116f8bc
    Base1::a
    0x00000001
    0x0116f8c0
    Base1::b,Base1::c
    0xcccc0302(-859045118)
    0x0116f8c4
    b2p , Base2::_vfptr虚関数テーブルアドレス
    -
    0x0116f8c8
    Base2::a
    0x00000001
    0x0116f8cc
    Base2::b,Base2::c
    0xcccc0302(-859045118)
    0x0116f8d0
    Derived::d
    0x00000001
    3.2説明
  • は、派生クラスオブジェクトにベースクラスオブジェクトが含まれていると考えることができ、ベースクラスオブジェクトは、派生クラスオブジェクトに継承順に配置する
  • .
  • 関数書き換えの場合、単一継承がベースクラス関数の虚関数書き換え
  • をそれぞれ書き換えるものと見なすことができる.
  • 継承ごとにダミーテーブル
  • が作成されます.
  • アップシフト実質的にオブジェクトオフセット量の変化、すなわちdynamic_castの役割.見てください:b 1 p,b 2 pのメモリにおける分布
  • 3.3テストコード
    //Base1     
        int b1_vfptr_address = *b1p;//      
        int b1_f1_address = ((int *)b1_vfptr_address)[0];//            
        fptr b1_f1p = (fptr)b1_f1_address;//  f  
        b1_f1p();//    f,   "Derived::f1()"               
    
        int b1_f2_address = ((int *)b1_vfptr_address)[1];//            
        fptr b1_f2p = (fptr)b1_f2_address;//  f  
        b1_f2p();//   "Base1::f2()" ,
    
        int b1_f3_address = ((int *)b1_vfptr_address)[2];//            
        fptr b1_f3p = (fptr)b1_f3_address;//  f  
        b1_f3p();//   "Derived::f3()" 
    
    //Base2     
        int b2_f1_address = ((int *)b2_vfptr_address)[0];//            
        fptr b2_f1p = (fptr)b2_f1_address;//  f  
        b2_f1p();//    f,   "Derived::f1()"               
    
        int b2_f2_address = ((int *)b2_vfptr_address)[1];//            
        fptr b2_f2p = (fptr)b2_f2_address;//  f  
        b2_f2p();//    f,   "Base2::f2()" ,
    
        int b2_f3_address = ((int *)b1_vfptr_address)[2];//            
        fptr b2_f3p = (fptr)b1_f3_address;//  f  
        b2_f3p();//    f,   "Derived::f3()" ,
        cout << hex << b1p << "," << hex << *(b1p) << endl;//Base1::_vfptr       ,
        b1p++;
        cout << hex << b1p << "," << hex << *(b1p) << endl;//Base1::a
        b1p++;
        cout << hex << b1p << "," << hex << *(b1p) << endl;//Base2::b,Base2::c
    
    
        cout << hex << b2p << "," << hex << *(b2p) << endl;//Base2::_vfptr       ,
        b2p++;
        cout << hex << b2p << "," << hex << *(b2p) << endl;//Base2::a
        b2p++;
        cout << hex << b2p << "," << hex << *(b2p) << endl;//Base2::b,Base2::c
        b2p++;
        cout << hex << b2p << "," << hex << *(b2p) << endl;//Derived::d
    
    
        b2p++;
        cout << hex << b2p << "," << hex << *(b2p) << endl;//Derived::d

    4.ダミー継承
    class Base{
    public:
        int a = 1;
        virtual void f1(){
            cout << "Base::f1()" << endl;
        }
        virtual void f2(){
            cout << "Base1::f2()" << endl;
        }
    };
    
    class Base1 :public virtual Base {
    public:
        int b=2;
    
        virtual void f3(){
            cout << "Base1::f3()" << endl;
        }
    
    };
    
    
    
    class Base2 :public virtual Base {
    public:
        int c = 3;
    
        virtual void f3(){
            cout << "Base2::f3()" << endl;
        }
    
    };
    class Derived : public Base1, public Base2{
    public:
        int  d=4;
         void f1(){
             cout << "Derived::f1()" << endl;
        }
    
         virtual  void  f4(){
             cout << "Derived::f4()" << endl;
         }
    };
    Derived  d;
    int* dp = (int *)&d;
    int* bp = (int *)(Base*)&d;
    int* b1p = (int *)(Base1*)&d;
    int* b2p = (int *)(Base2*)&d;

    4.1メモリ分布
    アドレス
    変数名

    0x012ffc30
    dp ,b1p , Base1::_vfptr虚関数テーブルアドレス
    指向Base 1::f 3()
    0x012ffc34
    指向するメモリは現在のアドレスからベースへのオフセット量を格納している.
    [0xfffffffc,0x00000018] , 0x012ffc34+0x00000018==0x012ffc4c
    0x012ffc38
    Base1::b
    0x00000002
    0x012ffc3c
    b2p , Base2::_vfptr虚関数テーブルアドレス
    指向Base 2::f 3()
    0x012ffc40
    指向するメモリには、現在のアドレスからBaseへのオフセットが格納されています.
    [0xfffffffc,0x0000000c] , 0x012ffc40+0x0000000c==0x012ffc4c
    0x012ffc44
    Base2::c
    0x00000003
    0x012ffc48
    Derived::d
    0x00000004
    0x012ffc4c
    bp , Base2::_vfptr虚関数テーブルアドレス
    指向Base::f 1()、Base::f 2()
    0x012ffc50
    Base::a
    0x00000001
    4.2説明
    ダミー継承の共通は、ベースクラスでメモリに1つしかありません.派生クラス共通ベースクラスに記録されたオフセット量によって共通ベースクラスが見つかります
    4.3テストコード
        cout << hex << dp << "," << hex << *(dp) << endl;//Base1::_vfptr   
        dp++;
        cout << hex << dp << "," << hex << *(dp) << endl;//              Base    
        cout << hex <int *)(*dp))[1]<// Base       1824
        dp++;
        cout << hex << dp << "," << hex << *(dp) << endl;//Base1::b
        dp++;
    
    
        cout << hex << dp << "," << hex << *(dp) << endl;//Base2::_vfptr   
        dp++;
        cout << hex << dp << "," << hex << *(dp) << endl;//              Base    
    
        cout << hex << ((int *)(*dp))[1] << endl;// Base       c, 12
    
        dp++;
        cout << hex << dp << "," << hex << *(dp) << endl;//Base2::c
        dp++;
        cout << hex << dp << "," << hex << *(dp) << endl;//Derived::d
        dp++;
        cout << hex << dp << "," << hex << *(dp) << endl;//Base::_vfptr   
        dp++;
        cout << hex << dp << "," << hex << *(dp) << endl;//Base::a