C++中虚函数工作原理和(虚)继承类的内存占用大小计算

一、虚继承情况下类的内存大小计算

当每个基类中有多个虚函数时,并且在虚继承的情况下,内存是如何分配的,如何计算类的大小,下面举例说明:

#include<iostream>
using namespace std;

class A
{
    
public:
    int a;
    virtual void aa(){};
};

class D
{
public:
    virtual void dd(){};
};


class C
{
public:
    virtual void cc(){};
};

class B : virtual public  A,virtual public  D, virtual public  C
{
    
public:
    int a;
    virtual void bb(){};

};
技术分享

sizeof(B) = 28,B类中虽然虚继承了多个基类(A、D、C),但是只有一个指向基类的虚基类指针,而却有多个虚函数表的指针,当B类未对三个基类A、D、C的虚函数进行重写的时候,它们四个类分别有指向自己虚函数表的虚函数指针。当B类对基类A的虚函数进行覆盖写的时候,内存发生了变化,此时sizeof(B) = 24 。B类的虚函数表就对A进行了修改。内存分配的新图如下:

class A
{
    
public:
    int a;
    virtual void aa(){};
};

class D
{
public:
    virtual void dd(){};
};


class C
{
public:
    virtual void cc(){};
};

class B : virtual public  A,virtual public  D, virtual public  C
{
    
public:
    int a;
    virtual void aa(){};

};

技术分享

二、普通继承情况下类的内存大小计算

当每个基类中有多个虚函数时,并且在普通继承的情况下,内存是如何分配的,如何计算类的大小,下面举例说明:

#include<iostream>
using namespace std;

class A
{
    
public:
    int a;
    virtual void aa(){};
};

class D
{
public:
    virtual void dd(){};
};


class C
{
public:
    virtual void cc(){};
};

class B : public  A,public  D, public  C
{
    
public:
    int a;
    virtual void bb(){};
};

技术分享

sizeof(B) = 20,B类中虽然虚继承了多个基类(A、D、C),但是只有一个指向基类的虚基类指针,而却有多个虚函数表的指针,当B类未对三个基类A、D、C的虚函数进行重写的时候,B类的虚函数的指针默认放在第一个继承的基类A的后面。当B类对基类A的虚函数进行覆盖写的时候,B类的虚函数指针对A类覆盖写的函数指针进行了覆盖,此时sizeof(B) = 20 。B类的虚函数表就对A进行了修改。内存分配的新图如下:

#include<iostream>
using namespace std;

class A
{
    
public:
    int a;
    virtual void aa(){};
};

class D
{
public:
    virtual void dd(){};
};


class C
{
public:
    virtual void cc(){};
};

class B : public  A,public  D, public  C
{
    
public:
    int a;
    virtual void aa(){};
};

技术分享


综上所述:虚继承和普通继承的差别在于是否有一个指向基类地址的虚基类指针vbptr。

在虚继承的情况下,如果派生类没有对基类的虚函数进行覆盖写,它由自己对应的虚函数表指针指向它的虚函数表,但是,当其对某个基类的虚函数进行覆盖写的时候,它的虚函数地址就覆盖写了对应基类的虚函数表中,不再有自己独立的虚函数表指针。

在普通继承的情况下,如果派生类没有对基类虚函数进行了覆盖写,它的虚函数地址后默认跟在第一个继承的基类后面,如果当对某个基类的函数进行覆盖写的时候,它的虚函数地址就覆盖写了对应基类的虚函数表中。在普通继承的情况下,派生类不具有自己独立的虚函数表指针。


下面是一个比较复杂的例子,在利用虚继承之后,又把虚继承几个类进行普通继承,注意此时普通继承是,就有了每个分别执行各自基类的虚基类指针。

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 Base3: virtual public CommonBase  
{  
public:  
    virtual void dump1() {  }  
    virtual void dump2() {  }  
private:  
    int b3;  
};
 
class Derived: public Base1, public Base2  ,public Base3
{  
public:  
    void print2() {  }  
    void dump2() {  }  
private:  
    int d;  
};

sizeof(Derived) = 44

技术分享



VS2010命令行下查看虚函数表和类内存布局

在学习多重继承下的Virtual functions时,需要分析派生类的虚函数表(vtable),但是在网上找了好几种Hack vtable方法,结果都不尽如人意。没想到MS Compiler(以VS2010为例)有打印vtable的编译选项,真是太好了!

1. 打开“Visual Studio Command Prompt (2010)”,如下

技术分享

该CMD下具有VS2010命令行下的一些编译、链接等工具,例如cl.exe。

 


2、使用cl命令的/d1 reportAllClassLayout或reportSingleClassLayoutXXX选项。这里的reportAllClassLayout选项会打印大量相关类的信息,一般用处不大。而reportSingleClassLayoutXXX选项的XXX代表要编译的代码中类的名字(这里XXX类),打印XXX类的内存布局和虚函数表(如果代码中没有对应的类,则选项无效)。

举例如下

         cl /d1 reportSingleClassLayoutBase1 160.cpp 


郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。