C++菱形继承内存布局的探索

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/houzijushi/article/details/82078595

C++菱形继承的探索

参考文章:

1、https://blog.csdn.net/haoel/article/details/3081328
2、https://blog.csdn.net/castle_kao/article/details/71024411

前言

C++虚继承下菱形继承的对象内存布局依赖于编译器。对于vs而言,虚继承通过虚基类表和虚基类指针(vbptr)实现;对于g++而言,可能会有另一种实现方式。本文基于文章【1】和文章【2】中描述的实验方法,在Linux和g++编译器环境下,测定了虚继承菱形继承生成对象的内存布局。

实验一:

运行环境:

ubuntu16.04,g++5.4.0,64位

代码:
#include <iostream>
using namespace std;


class B
{
public:
    int ib;
    char cb;
public:
    B() :ib(0), cb('B') {}

    virtual void f() { cout << "B::f()" << endl; }
    virtual void Bf() { cout << "B::Bf()" << endl; }
};
class B1 : virtual public B
{
public:
    int ib1;
    char cb1;
public:
    B1() :ib1(11), cb1('1') {}

    virtual void f() { cout << "B1::f()" << endl; }
    virtual void f1() { cout << "B1::f1()" << endl; }
    virtual void Bf1() { cout << "B1::Bf1()" << endl; }


};
class B2 : virtual public B
{
public:
    int ib2;
    char cb2;
public:
    B2() :ib2(12), cb2('2') {}

    virtual void f() { cout << "B2::f()" << endl; }
    virtual void f2() { cout << "B2::f2()" << endl; }
    virtual void Bf2() { cout << "B2::Bf2()" << endl; }

};

class D : public B1, public B2
{
public:
    int id;
    char cd;
public:
    D() :id(100), cd('D') {}

    virtual void f() { cout << "D::f()" << endl; }
    virtual void f1() { cout << "D::f1()" << endl; }
    virtual void f2() { cout << "D::f2()" << endl; }
    virtual void Df() { cout << "D::Df()" << endl; }

};

int main(void)
{
    typedef void(*F)();
    D d;
    B1 *p = &d;   //0

    long *op = reinterpret_cast<long*>(p);    //1    
    long *vfptr = reinterpret_cast<long*>(*op);//2
    F fun = reinterpret_cast<F>(*(vfptr + 0));   //3

    fun();

    cout << "sizeof(T): " << sizeof(D) << endl;

    return 0;
}

程序运行结果:
D::f()
sizeof(T): 56
实验步骤:

1、依次将代码中【3】处的vfptr偏移值+1,可测出vfptr指向的虚函数表的所有函数信息;
2、变换【0】处指针p的类型,改为D、B1、B2和B,重复1步骤,可测得所有的虚函数表中函数信息。

结论:

上述实验中,D对象内存模型为:
这里写图片描述

经过内存补齐计算,在64位环境中D对象内存位56byte,与上述内存布局吻合。
1、派生类对象创建过程中,首先创建直接基类(非virtual继承)的对象模型。直接基类对象创建过程可不考虑派生类因素,按照其自身的创建方法创建,依次向上。
2、创建虚基类时候,同样不用考虑派生类,按照自身原本的方法创建。此部分内存放在派生类对象内存最后。
3、创建派生类,类中的虚函数更新到基类的虚函数表中。新添加的部分放在第一个直接基类虚函数表最后,如上述实验。
4、上述实验中,B1* p = &d、B2* p = &d和B* p = &d不相同,但各自指向各自部分的虚函数表首地址。B1* p = &d和D* p = &d相同。

实验二:

代码1:

#include <iostream>
using namespace std;

class A {
    int a;

};

class B : virtual public A {
public:
    virtual void func() {}

};

int main() {
    cout << sizeof(B) << endl;

    return 0;
}

不同环境下有不同结果。

实验三:

测试构造函数执行顺序
代码:

#include <iostream>
using namespace std;

class test {
public:
    test() {cout << "test" << endl;}
    test(int m) {cout << "test" << m << endl;}

};



class A {
public:
    A(int) {cout << "A" << endl;}
private:
    test t1;
};

class B {
public:
    B(int) {cout << "B" << endl;}
private:
    test t2;
};

class C :virtual public A, public B {
public:
    C(int a) : A(a), B(a) {cout << "C" << endl;}
private:
    test t3;
};


int main() {
    C a(3);

    return 0;
}

猜你喜欢

转载自blog.csdn.net/houzijushi/article/details/82078595