zoukankan      html  css  js  c++  java
  • 多重虚继承下的对象内存布局

    《深入C++对象模型》绝对是一本值得深读的一本书,书里多次出现一句话,“一切常规遇见虚继承,都将失效”。这是一个有趣的问题,因为C++标准容忍对象布局的实现有较大的自由,出现了各编译器厂商实现的方式不同。

    今天谈谈visual studio2013多重虚继承下对象布局。有错不要客气,不要吝啬你的留言。

     

    class y和class z都是从class x虚继承来的子类(也叫派生类),class A是class y和class z的多重继承子类。为了简化问题,下面的data member都是none_static data member。不提static data member是为了描述起来简单~

    #include<iostream>
    class x
    {
    public:
        int _x;
    };
    class y : public virtual x
    {
    public:
        int _y;
    };
    class z : public virtual x
    {
    public:
        int _z;
    };
    class A:public z,public y
    {
    public:
        int _a;
    };
        int main()
    {
        std::cout <<"sizeof(x): "<< sizeof(x) << std::endl;
        std::cout <<"sizeof(y): "<< sizeof(y) << std::endl;
        std::cout <<"sizeof(A): "<< sizeof(A) << std::endl;
        std::cout <<"&A::_x :"<< &A::_x << std::endl;;
        std::cout << "&A::_y :" << &A::_y << " " << std::endl;
        std::cout << "&A::_z :" << &A::_z << " " << std::endl;
        std::cout <<"&A::_a :"<< &A::_a << " " << std::endl;
    
        getchar();
        return 0;
    }

    输出:

    sizeof(x): 4
    sizeof(y): 12
    sizeof(A): 24

    sizeof(x)和sizeof(y)的结果在我们的意料中:x中没有虚函数,所以sizeof(x) = sizeof(int)。class y虚继承自class x,它需要一个指针指向类似于virtual base table的指针(下面简称vbptr),指向自己实际的x对象地址,所以sizeof(y)=2*sizeof(int) + sizeof(vbptr),我使用32位编译器生成的代码,所以sizeof(y)=12.

    按照我开始的料想,sizeof(A)应该等于sizeof(int)*4(分别是:_x,_y,_z,_a) + sizeof(vbptr)=20。这里的vbptr指向了唯一的x实例。

    (注意啦,我要开始了)

    C++标准中有规定,子类的对象模型中,应该保持父类对象的布局。在上面,我们说了sizeof(y)=2*sizeof(int) + sizeof(vbptr)。但,我们还说了,常规遇见虚继承就可能失效。

    根据虚继承的概念,class x的data member只会出现class A中一次,所以sizeof(A)=sizeof(y::vbptr+y::_y + z::vbptr + z::_z + x::_x)

    用公式表示出来sizeof(A) = sizeof(base class a1) +sizeof(base class a2) +...+ sizeof(base class an) + sizeof(virtual inheritance(虚继承父类))  - N*sizeof(virtual inheritance(虚继承父类)) 

    所以A的对象大小等于:继承部分大小再加上A中自己data member 大小。

    要非常注意的是,这里的大小是指数据对齐后的大小,这里的对齐需要注意两点:第一是,类对象中包含类对象时的对齐;第二是,父类对象的内存布局应该被保持:从普通继承(相对虚继承)的类中拆开公有的类对象,他们分别会保持内存布局。(非常的绕口,我决定把这个问题另开一篇帖子讨论,此处稍后放上链接)

    而vbptr因为出在了两个父类中,已经不需要额外的一个vbptr指向了。

    你是不是要问这个vbptr(虚基类表指针)到底是干什么的?

    我们知道虚继承的父类x,无论它被派生多次,它在最后多重继承的子类中,只存在一份实例。这样可能出现这样的情况:

    y object_y;

    A object_A;

    object_A._x =1;

    object_y = object_A;//这里发生类对象的剪切,将子类中的非父类成员剪去,剩下的赋给父类。

    可能你还没有明白,没关系,我再啰嗦一句~

    抱歉没有很好的绘图工具,我们使用“|“来隔开内存中的成员。

     object_y 的内存布局应该是这样的:vbptr | _y | _x,注意先实例化父类,再实例化子类的原则因为虚继承的原因失效了!这里实例化虚继承的子类对象时,先实例化非公有部分(也就是除了class x的data member部分:class y的int _y),再实例化,虚继承的父类的data member部分,也就是公有部分class x 中的int _x。

    而,object_A的内存布局应该是这样的:vbptr_z | _z | vbptr_y | _y | _a | _x,注意我们多重继承的先后顺序:class A:public z,public y,这个顺序决定了z的对象布局在y对象前面。因为虚继承的原因,我们对象布局发生了改变:先实例化不共享部分(也就是class y和class z中除了class x的成员),这个部分依然是先父类再子类。最后才是虚继承的公共部分:class x的成员。

    我们发现,直接进行内存布局的剪切行不通(红色部分),这需要编译器介入,但编译器也不是神仙啊,它需要信息去找_x在哪里,这就是vbptr中存的信息——它告诉编译器去哪里找_x,这里的信息可能是偏移,也可能是指针。为什么是表呢?而不是直接指向成员的指针呢?因为,如果你再虚继承几个父类,你的这个指针会越来越多,对象的规模也就越来越大,这不能容忍。使用表可以始终只占一个指针大小,只是需要间址查询,但是这完全可以由编译器优化之。

     

  • 相关阅读:
    js小数点失精算法修正
    ActiveX控件之ActiveXObject is not defined
    js通过日期计算属于星期几
    标准日期格式化
    js阿拉伯数字转中文大写
    RPC 原理的前生今世
    大型网站架构系列:20本技术书籍推荐
    Zookeeper核心机制
    建造者模式
    模板方法模式
  • 原文地址:https://www.cnblogs.com/wangpei0522/p/4435390.html
Copyright © 2011-2022 走看看