• Visual C++ 8.0对象布局的奥秘:虚函数、多继承、虚拟继承(VC直接输出内存布局)


    原文:VC8_Object_Layout_Secret.html

    哈哈,从M$ Visual C++ Team的Andy Rich那里又偷学到一招:VC8的隐含编译项/d1reportSingleClassLayout/d1reportAllClassLayout。看个复杂的例子吧(如下),现在假设我们想知道Derived类的对象布局,怎么办? 在Project Properties->C++->Command Line->Additional Options里面加上/d1reportSingleClassLayoutDerived吧!

    class CommonBase
    {
        int co;
    };
     
    class Base1: virtual public CommonBase
    {
    public:
        virtual void print1() {}
        virtual void print2() {}
    private:
        int b1;
    };
     
    class Base2: virtual public CommonBase
    {
    public:
        virtual void dump1() {}
        virtual void dump2() {}
    private:
        int b2;
    };
     
    class Derived: public Base1, public Base2
    {
    public:
        void print2() {}
        void dump2() {}
    private:
        int d;
    };
     
    int _tmain(int argc, _TCHAR* argv[])
    {
        return 0;
    }
    

    F5编译之,你会惊奇地发现,Output里面有如下字样:

    01 class Derived size(32):
    02    +---
    03    | +--- (base class Base1)
    04  0 | | {vfptr}
    05  4 | | {vbptr}
    06  8 | | b1
    07    | +---
    08    | +--- (base class Base2)
    09 12 | | {vfptr}
    10 16 | | {vbptr}
    11 20 | | b2
    12    | +---
    13 24 | d
    14    +---
    15    +--- (virtual base CommonBase)
    16 28 | co
    17    +---
    18 
    19 Derived::$vftable@Base1@:
    20  0 | &Base1::print1
    21  1 | &Derived::print2
    22 
    23 Derived::$vftable@Base2@:
    24  0 | &Base2::dump1
    25  1 | &Derived::dump2
    26 
    27 Derived::$vbtable@Base1@:
    28  0 | -4
    29  1 | 24 (Derivedd(Base1+4)CommonBase)
    30 
    31 Derived::$vbtable@Base2@:
    32  0 | -4
    33  1 | 12 (Derivedd(Base2+4)CommonBase)
    34 
    35 Derived::print2 this adjustor: 0
    36 Derived::dump2 this adjustor: 12
    

    看到了吗? VC8居然输出了Derived对象的完整布局! 我们终于可以不必两眼一抹黑般的去peek/poke了….第1行表明,Derived对象总占用了32字节;其由三部分组成,分别是行3-行7、行 8-行12、行13、行28;其中前二者分别是基类Base1、Base2的布局,最后的行28为虚拟基类Common的布局。

    以基类Base1部分为例,可发现其由一个虚函数表指针vftable和虚基表指针vbtable构成,先看Base1部分的vftable所指向的虚表 $vftable@Base1(行19),不难发现,其中的表项2已经被Derived::print2给override了;再来看Base2部分的 vftable所指向的虚表$vftable@Base2(行23),可发现,同样的,Base2::dump2被Derived::dump2给 override了。这不明摆着就是虚函数机制嘛,heh~

    值得注意的是,这个例子同时说明,多继承场合下,其实在单一对象中是存在多个this指针的….行35-36给出了如何将Derived的this指针校正为其基类子对象this指针的偏移量,也就是说,根据行36,假设有个Derived d,那么d.dump1()实际上应该理解成通过虚表$vftable@Base2对((Base2*)(((char*)&d)+12))- >dump1()的调用….即传递给所有Base2成员函数的this指针应该是(Base2*)((char*)(&d)+12),这里可能我写得恐怖了点,意思到了就成….这不,普通继承、多继承、对象Slicing的语义都在这个布局里面了,看仔细了哈~

    OK,多继承看完了,继续看虚拟基类是如何布局的。虚基Common在Derived的布局中,位于Derived本身数据成员之后的位置。Base1、 Base2中均保存了一个vbtable指针,其分别指向各自所使用的虚基表$vbtable@Base1和$vbtable@Base2,为什么要指向一个虚基表? 很简单,因为Base1、Base2有可能会同时继承多个不同的虚拟基类…..这充分体现了C++对象布局的复杂性….在每个虚基表中,保存了所继承的虚拟基类部分相对于子类部分vbtable指针的偏移值,以Base2为例,我们知道Base2的vbtable在Derived中的偏移值为16 (行10),则根据$vbtable@Base2,虚基Common部分距离Base2 vbtable指针的偏移值为12,则有虚基Common在Derived中的总偏移值为16+12。与普通多继承同理,我们在调用非虚拟的虚基成员函数时,必须将Derived的this指针调整为指向虚基部分的this指针,只有这样才能成功地访问虚基自身的数据成员和虚基的虚拟函数(通过虚基自己的 vftable,为简单起见,上例中我就没弄那么复杂了,大家可以自己玩玩,明白如何举一反三即可)

    看完了上述解释,是不是感觉比啃Inside C++ Object Model来得更快更直观啊?hehe

    http://cplusplus.wikidot.com/cn:vc8-object-layout-secret

  • 相关阅读:
    Eclipse用法和技巧二十六:浅谈快捷键
    Eclipse用法和技巧二十五:eclipse图标的含义
    Eclipse用法和技巧二十四:当git遇上eclipse
    Eclipse用法和技巧二十三:查看JDK源码
    Eclipse用法和技巧二十二:快速调整字体大小
    Eclipse用法和技巧二十一:工程的展示途径
    94. Binary Tree Inorder Traversal
    110. Balanced Binary Tree
    595. Big Countries
    720. Longest Word in Dictionary(important!!!about sort)
  • 原文地址:https://www.cnblogs.com/findumars/p/5836007.html
Copyright © 2020-2023  润新知