1 工具准备

工欲善其事,必先利其器,我们用到的工具是VS。下面介绍如何使用VS查看类内存分布,学会了你也能自己分析。

一、右击项目->属性

    

二、配置属性->C/C++->命令行

    
    
    在其他选项这里写上/d1 reportAllClassLayout:可以看到所有相关类的内存布局
    写上/d1 reportSingleClassLayoutXXX(XXX为类名),则只会打出指定类XXX的内存布局。

2 进入正题

一、一个类没有虚函数

class Base {
	int a;
	int b;
public:
	void Function() {}
};
    模型是这样的:
        
    可能你看到的是这样的:
        
       看到它了吗?点下去:
            
    这个模型如我们所愿

二、一个类有虚函数

class Base {
	int a;
	int b;
public:
	virtual void VirtualFunction1() {}
	virtual void VirtualFunction2() {}
};
    模型是这样的:
        
            vfptr(虚函数表指针) 放在开头,偏移0
            vftable(虚函数表) 下面依次是虚函数,0 1 编号
                (第一个0的解释:指向它的 vfptr 在内存中的偏移:0)

三、单继承时

class Base {
	int a;
	int b;
public:
	virtual void VirtualFunction() {}
};

class Derived1 :public Base {
	int c;
public:
	virtual void VirtualFunction() {}
	virtual void VirtualMine() {}
};
    父类:
        
    子类:
        
            首先是继承自父类的:虚函数表指针、变量a b      -- (base class Base)说明了这是基类Base
            其次是自己的:变量c
        
            还继承了父类的 vftable ,重写虚函数后覆盖了父类的虚函数,并在里面添加了自己的虚函数
            第一个0表示:指向此 vftable 的指针 vfptr 在内存里面的偏移
            下面的 0 1:依次编号

     多态实现的理解:
            Base * p = new Derived1();
            父类指针指向子类变量,当调用 p->VirtualFunction(); 时,即:p->vfptr->VirtualFunction(); 
            既然是父类指针,那么会按照父类内存布局去找vfptr,而C++把它放在0偏移处,就找到了子类的vfptr,最终调用到了子类的虚函数。
    
    子类不重写虚函数:
            
                不重写虚函数,vftable 里面就没有覆盖父类虚函数,这时候调用这个函数,就是父类的虚函数

四、多继承时(菱形继承)

class Base {
	int a;
	int b;
public:
	virtual void VirtualFunction() {}
};

class Derived1 :public Base {
	int c;
public:
	virtual void VirtualFunction() {}
};

class Derived2 :public Base {
	int d;
public:
	virtual void VirtualFunction() {}
};

class LastDerived :public Derived1, public Derived2 {
	int e;
public:
	virtual void VirtualFunction() {}
};
    Derived1:
        
        内存情况和上面单继承一样的
   
    Derived2:
        
        同Derived1

    LastDerived:
        
        首先放的是继承自第一个父类 Derived1的:里面放了 Derived1 的所有继承下来的东西,包括 vfptr
        首先放的是继承自第一个父类 Derived2的:里面放了 Derived2 的所有继承下来的东西,包括 vfptr
        最后放的自己的变量e

        两个 vftable :
            一个是从Derived1继承下来的;
            一个是从Derived2继承下来的,-16表示指向此虚函数表的指针vfptr在内存里面的偏移。
            
        总的来说:继承了所有父类的东西,连基类的内容也继承了两套,冗余。

五、虚继承时

class Base {
	int a;
	int b;
public:
	virtual void VirtualFunction() {}
};

class Derived1 :virtual public Base {
	int c;
public:
	virtual void VirtualFunction() {}
};

class Derived2 :virtual public Base {
	int d;
public:
	virtual void VirtualFunction() {}
};

class LastDerived :public Derived1, public Derived2 {
	int e;
public:
	virtual void VirtualFunction() {}
};
    Derived1:虚继承自 Base
        
        首先放的不是父类的内容了
        首先放的是自己的: vbptr(虚基类指针,指向一个偏移量表 vbtable) 、变量c
        其次放的是继承自父类的: vfptr、 变量a b
        
        vbtable(虚基类表):
            存放是基类的 vfptr 相对于 vbptr 的偏移
        
        vftable(虚函数表):
            首先是指向自己的 vfptr 在内存中的偏移
            其次是虚函数地址

        扩展:若此时,再写一个新的虚函数,它不是虚继承自基类的虚函数,会再为 Derived1 生成一个虚函数表,它有了另一个身份:新的虚基类
            
            
                可以看出,除了上面分析的几个指针、表外,又多了一个 vfptr 放在开始,下面也多了一个虚函数表,它又多了一个身份
                由于有两个 vftable ,在后面用@Derived@、@Base@指明了

        Derived2:虚继承自 Base
            
            和Derived1是一样的

        LastDerived:实继承自Derived1 Derived2
            
            可以看到的是,和非继承一样,先放父类,再放子类。只是父类基类的部分被剥离放在了最下面:
            
            首先放的继承自第一个父类 Derived1 的 :vbptr 、变量c    (由于Derived是虚继承自Base,所以先放的是vbptr,参照上一步 )
            其次放的继承自第二个父类 Derived2 的: vbptr 、变量d
            然后是自己的变量e
            最后存放了继承自基类(与非虚继承不同是时,非虚继承是把基类部分放在父类继承来的内存里面)的:vfptr 、变量a b
        
            Derived1 的 vbtable:
                存放是基类的 vfptr 相对于 vbptr 的偏移:20 (20 - 0)
        
            Derived2 的 vbtable:
                存放是基类的 vfptr 相对于 vbptr 的偏移:12 (20 - 8)
            
            vftable:
                -20表示 :指向自己的 vfptr 在内存中的偏移,并且这个类已经重写了基类的虚函数
            
            可以总结的是:
                虚继承的作用是减少了对基类的重复,仅一份放在最后,代价是增加了 vbptr(虚基类指针)、vbtable(虚基类表)的负担

六、总结(当基类有虚函数时)

        1. 每个类都有虚指针和虚表。
    
        2. 如果不是虚继承,那么子类将父类的虚函数指针继承下来,并指向自身的虚函数表(发生在对象构造时)。有多少个虚函数,虚函数表里面的项就会有多少。
        多重继承时,可能存在多个的基类虚表与虚指针。
    
        3. 如果是虚继承,那么子类除了有虚函数指针、虚函数表外,还有一根虚基类指针 vbptr 指向一个虚基类表 vbtable(存放 vfptr 相对于 vbptr 的偏移,用来寻找基类 vfptr )。
        多重继承时虚函数指针、虚函数表有且只有一份。
        
        4. 非虚继承时:都是先放父类继承下来的东西,再放自己的东西。
        虚继承时:先放自己的 vbptr (虚继承特有)、自己的东西,再放父类的东西。

七、虔心学习,若有错误,敬请指正。