C++对象内存布局:单继承,多继承,虚继承

0.前言

本文的讨论了类实例对象的内存分布,程序由visual studio2013编写。如果你想看懂 ,请认真看内存分布中的地址

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说明

  1. d与_vfptr 的地址相同,说明对象的起始位置存放是虚函数表地址
  2. 不管是私有继承还是,私有变量,都能在2.3的测试代码中直接访问
  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 &db1p , 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说明

  1. 可以认为派生类对象中包含基类对象,基类对象 按继承顺序放在派生类对象中
  2. 函数重写时,可以看成单继承分别对基类函数中的虚函数重写
  3. 每次继承都创建一个虚表
  4. 向上转型 实质为对象偏移量的改变,即dynamic_cast的作用。 看:b1p ,b2p 在内存中的分布

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虚函数表地址 指向Base1::f3()
0x012ffc34 指向的内存存着放当前地址到Base的偏移量 [0xfffffffc,0x00000018] , 0x012ffc34+0x00000018==0x012ffc4c
0x012ffc38 Base1::b 0x00000002
0x012ffc3c b2p , Base2::_vfptr虚函数表地址 指向Base2::f3()
0x012ffc40 指向的内存存放着当前地址到Base的偏移 [0xfffffffc,0x0000000c] , 0x012ffc40+0x0000000c==0x012ffc4c
0x012ffc44 Base2::c 0x00000003
0x012ffc48 Derived::d 0x00000004
0x012ffc4c bp , Base2::_vfptr虚函数表地址 指向Base::f1(),Base::f2()
0x012ffc50 Base::a 0x00000001

4.2 说明

虚继承的共同在基类在内存中只有一份。派生类通过记录到 共同基类的偏移量来找到共同基类

4.3 测试代码

    cout << hex << dp << "," << hex << *(dp) << endl;//Base1::_vfptr 地址
    dp++;
    cout << hex << dp << "," << hex << *(dp) << endl;//指向的内存存放着 当前地址到Base的偏移量
    cout << hex <<((int *)(*dp))[1]<<endl;//到Base的偏移量 输出18,即24
    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

猜你喜欢

转载自blog.csdn.net/guiqulaxi920/article/details/76806467