在其他选项这里写上/d1 reportAllClassLayout,它可以看到所有相关类的内存布局,如果写上/d1 reportSingleClassLayoutXXX(XXX为类名),则只会打出指定类XXX的内存布局。近期的VS版本都支持这样配置。
运行程序的话就会自动生成一张虚函数表了:
#include <iostream>
using namespace std;
class Base {
public:
int a = 0;
int b = 0;
virtual void f() { cout << "Base::f" << endl; }
virtual void g() { cout << "Base::g" << endl; }
virtual void h() { cout << "Base::h" << endl; }
};
class Derive : public Base {
void g() { cout << "Derive::g" << endl; }
virtual void g1() { cout << "Derive::g" << endl; }
};
int main()
{
system("pause");
return 0;
}
这个内存结构图分成了两个部分,上面是内存分布,下面是虚表。
单一继承(含成员变量+虚函数+虚函数覆盖)
通过代码查看的虚函数表是这样的:
多继承(含成员函数+虚函数+虚函数覆盖)
2个int型,一个short型(2字节padding后占4个字节),2个虚函数表,所以长度为5*4 = 20;虚函数表是这样的:
内存布局是这样:
深度为2的继承(成员变量+虚函数+虚函数覆盖)
3个int型,1个short型(2字节padding后占4个字节),2个虚函数表;代码显示的类的布局是这样:
内存布局:
class A {
public:
virtual void f1() { cout << "A:f1" << endl; };
virtual void f2() { cout << "A:f2" << endl; };
virtual void f3() { cout << "A:f3" << endl; };
};
class B {
public:
virtual void g1() { cout << "B:g1" << endl; };
virtual void g2() { cout << "B:g2" << endl; };
virtual void f2() { cout << "B:f2" << endl; };
};
class C :public A, public B {
virtual void f1() { cout << "C:f1" << endl; };
virtual void g1() { cout << "C:g1" << endl; };
};
class D :public C {
virtual void f1() { cout << "D:f1" << endl; };
virtual void g2() { cout << "D:g2" << endl; };
};
显示的内存分布是这样的:
重复继承(含成员变量+虚函数+虚函数覆盖)
由于基类中的m_nAge在内存分布中出现了两次,所以最后的结果是5个int类型和2个虚函数表,共计28字节。
单一虚继承(含成员变量+虚函数+虚函数覆盖)
所谓的虚继承就是把继承语法前加上virtual关键字,例如class B:virtual public A{…};
虚拟继承的出现就是为了解决重复继承中多个间接父类(可以解决菱形问题)的问题的 。内存分布是这样的:
这里需要解释下,因为出现了vfptr与vbptr,vfptr常见,但是vbptr却是第一次见,它指向CChildren的vbtable,另一个CChildren的vfptr位于0地址偏移处,它指向CChildren的vftable。从截图中也可以看出有2个vftable与1个vbtable(一共3个表)。vbtable中的8表示vbptr与基类的vfptr(这里即_vfptr::CParent)之间的偏移量(记录了基类vfptr的地址,即二重指针)。
另外提及一下,如果CChildren里全部是重载基类中的虚函数的话,或者说没有新的虚函数的话,_vfptr::CChildren指向的虚函数表就是空的,所以计算大小的时候可以不用算进去,因为实际上并没有创建相应的表格。
举个例子:
class A {
public:
virtual void f1() { cout << "A:f1" << endl; };
virtual void f2() { cout << "A:f2" << endl; };
virtual void f3() { cout << "A:f3" << endl; };
};
class B:virtual A {
public:
//virtual void g1() { cout << "B:g1" << endl; }; //如果打开注释,那么B就会有自己的vfptr
void f2() { cout << "B:f2" << endl; };
void f3() { cout << "B:f3" << endl; };
};
内存分布为:
即此时B类没有自己的vfptr。
多虚继承(含成员变量+虚函数+虚函数覆盖)
(1)CParent2是虚继承,CParent1是一般继承
(2)CParent1是虚继承,CParent2是一般继承
(3)CParent1和CParent2都是虚继承
从这里可以看出vbtable确实是存储了指向相应的基类的vfptr的地址(二重指针)。
菱形的虚拟多重继承(含成员变量+虚函数+虚函数覆盖)