《深度探索C++对象模型》:Data语意学

首先,来看一段代码:

class X {};
class Y : public virtual X {};
class Z : public virtual X {};
class A : public Y, public Z {};

// sizeof(X)的结果为1
// sizeof(Y)的结果为8
// sizeof(Z)的结果为8
// sizeof(A)的结果为12
你可能会问,class X难道不是一个空类吗,为什么sizeof值不是0

实际上,class X并不为空,它被编译器安插了一个隐藏的1 byte,这样使得class X的每个object在内存中拥有不同的地址。但是为什么class Yclass Zsizeof大小是8呢?让我们来看看他们的关系。

       

从上图中看出,实际上class Yclass Z的大小受到三个因素的影响:

        1、语言本身造成的额外负担

                当语言支持virtual base classes特性时,就会需要一些额外的负担来实现,这表现在某种形式的pointer上,在32位机器上,指针占有4 bytes,它或指向virtual base class object,或指向一个相关的表格,该表格中存放的若不是virtual base class object的地址,就是偏移位置(offset)。

        2、编译器对于特殊情况提供的优化处理

                virtual base class object1 bytes也出现在class Yclass Z中,传统上它被放在derived class的尾部部分。

        3、Alignment的限制

                class Yclass Z的大小本应为5 bytes,但为了使在内存中更有效的存取,会将其调整为某数的整数倍,在32位机器上调整为4 bytes的整数倍。


通过上面图和三点说明,现在也就理解了为什么class Yclass Z8 bytes了。

那么继续往下,我们想知道class A12 bytes是怎么来的?


对于virtual inheritance(虚拟继承)而言,在一个derived class中只会存在一份virtual base class subobject,因此,class A的大小由以下几点决定:

        1、被大家共享的唯一一个class X实例,大小为1 byte;

        2、base class Y大小减去“因virtual base class X而配置”的4 bytes,剩下的大小为4 bytes;base class Z同理;

        3、class A的Alignment边界调整。

通过上面三点,可知class A的大小为12 bytes了。


        看到这你,你大概会问,由于字节对齐那些alignment padding填补的bytes不就是拜拜浪费了吗?这就好比你去ATM机取款,你只需要取93块钱就可以,但是它会提100给你(在卡里有钱的情况下),你只想要130,但是你不得不取200,就是这样。

        讲道理,这确实是个问题,本来内存就是个稀缺分寸必争的东西。对此,某些编译器(例如我用的vs2013)为我们提供了对empty virtual base class的特殊支持,就是将一个empty virtual base class视为derived class object最开头的一部分,也就是说derived class现在有member了,它不再是空了,也就不需要编译器安插那1 byte,自然也就不必为alignment padding提供3 bytes了。


        现在它们的格局是上图这样的了。class Yclass Zsizeof就是4 bytes了,当然class X还是一个empty class编译器依然会安插1 byte。那么class Asizeof又会是多大呢?聪明的你也应该想到了,是8 bytes,因为它不存在base class subobject1 bytes了。

        其实,正是这种编译器之间存在的差异,让我们看到了C++对象模型的逐渐演化。C++ Standard并不强制规定如“base class subobject”的排列顺序,也不规定virtual functionvirtual base classes的实现细节,这些都有厂商自定。于是我们会在《深度探索C++对象模型》书中经常看到,C++ Standard并不规定.....,这些细节.....C++ Standard并不提供......等等。当然这并不影响我们去了解C++对象模型的热情,而且由于厂商各种解决办法,各种优化的尝试,使得很多不是C++ Standard的东西,由于良好的优化逐渐的被视为标准的一部分了(虽然它并不是),因为各家编译器都在用这种优化,例如virtual function tableNRV优化方案就是极好的例子。


最后,我们再来复习一下之前文章说到过的data member

class Foo {
private:
    // nonstatic data member
    char a;
    char b;
    char c;
    int d;
    // static member
    static int e;
    static char f;
public:
    // nonstatic member function
    Foo() {}
    void fun1() {}
    // static member function
    static void fun2() {}
    // virtual member function
    virtual void fun3() {}
};
        上面的class Foo比较简单,我们很容易口算得sizeof(Foo)12 bytes。现在我们做一点点变形,首先将最后一个virtual member function注释掉,那么sizeof(Foo)将会是8 bytes;再来,在之前变形的基础上,我们将char cint d这两行交换一下变成int d在前,char c在后,那么sizeof(Foo)将会是12 bytes,咦。。。等等,为什么是12 bytes呢?这个也是alignment padding的功劳。现在我将virtual member function取消注释,然后再给出Foo的对象模型,这样你就会非常明了了。

// 定义一个Foo class object
Foo foo;
        C++对象模型尽量以空间优化和存取速度优化来表现nonstatic data members,并且和C语言的struct保持兼容性,直接将nonstatic data member存放在每一个class object中,对于继承而来的nonstatic data members(不论是virtual还是nonvirtual base object),也是如此。

        至于static data members,则被放置于global data segment中,不影响object的大小,不管程序中该class有多少个objects产生,static data members永远只有一份实例(甚至即使没有该classobject,其static data members也已存在);对于class member functions,只有声明为virtual或继承而来的virtual functions存在于每一个objectvtbl中,其余的nonvirtual functions属于该class的。这就好比钓鱼岛是中国的,苍井空是世界的。

参考资料:

[1] 深度探索C++对象模型,[美]Stanley B. Lippman著,侯捷译;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值