以下测试都是基于VS,Win32(地址就是四个字节大小)
注意:虚基指针指向虚基类,虚函数指针指向虚函数表
Linux与vs的唯一区别是,在Linux下虚函数指针与虚基指针合并了
配置:项目->(右键)属性->配置属性->C/C++->命令行
/d1 reportSingleClassLayoutXXX 或者/d1 reportAllClassLayout
reportSingleClassLayoutXXX:查看单个类的内存布局
reportAllClassLayout:查看所有类的内存布局
以下使用控制变量法测试
测试一、虚继承与继承的区别
1.1、单个继承,不带虚函数
#pragma vtordisp(off)
#include <iostream>
using std::cout;
using std::endl;
class A
{
public:
A() : _ia(10) {}
/*virtual*/
void f()
{
cout << "A::f()" << endl;
}
private:
int _ia;
};
class B
: /*virtual*/ public A
{
public:
B() : _ib(20) {}
void fb()
{
cout << "A::fb()" << endl;
}
/*virtual*/
void f()
{
cout << "B::f()" << endl;
}
private:
int _ib;
};
int main(void)
{
cout << sizeof(A) << endl;
cout << sizeof(B) << endl;
B b;
return 0;
}
生成解决方案:
输出里面查看:
1>class B size(8):
1> +---
1> 0 | +--- (base class A)
1> 0 | | _ia
1> | +---
1> 4 | _ib
1> +---
8个字节,4个字节的_ia,4个字节_ib,其他的啥也没有
1.2、单个虚继承,不带虚函数
放开虚继承
内存布局:
多了一个虚基指针,指向虚基表,虚基表的第一条内容表示的是该虚基指针距离所在的子对象的首地址的偏移,虚基表的第二条内容表示的是该虚基指针距离虚基类子对象的首地址的偏移
结论:
1. 多了一个虚基指针,指向虚基表
2. 虚基类数据成员位于派生类存储空间的最末尾(先存不变的后存共享的)
测试二:单个虚继承,带虚函数
2.1、单个继承,带虚函数
class A
{
public:
A() : _ia(10) {}
virtual
void f()
{
cout << "A::f()" << endl;
}
private:
int _ia;
};
class B
: /*virtual*/ public A
{
public:
B() : _ib(20) {}
void fb()
{
cout << "A::fb()" << endl;
}
virtual
void f()
{
cout << "B::f()" << endl;
}
private:
int _ib;
};
内存布局如下:
因为有虚函数,就会继承基类的虚函数指针,指向虚表,虚表是从A复制过来,和A的虚表地址不一样,B中没有重定义f()的话,B中虚表的虚函数仍然是&A::f(),B中重定义f()的话,在B的虚表中&B::f()将&A::f()覆盖了(注意,有虚函数的每个类都有自己的虚函数指针和虚表,所有对象共享类的虚表)
2.2、单个继承,带虚函数(自己新增虚函数)
class A
{
public:
A() : _ia(10) {}
virtual
void f()
{
cout << "A::f()" << endl;
}
private:
int _ia;
};
class B
: /*virtual*/ public A
{
public:
B() : _ib(20) {}
void fb()
{
cout << "A::fb()" << endl;
}
virtual
void f()
{
cout << "B::f()" << endl;
}
virtual
void fb2()
{
cout << "B::fb2()" << endl;
}
private:
int _ib;
};
内存布局如下:
基类布局在前面,新增的虚函数放在类B自己的虚函数表里面
2.3、单个虚继承,带虚函数
class A
{
public:
A() : _ia(10) {}
virtual
void f()
{
cout << "A::f()" << endl;
}
private:
int _ia;
};
class B
: virtual public A
{
public:
B() : _ib(20) {}
void fb()
{
cout << "A::fb()" << endl;
}
virtual
void f()
{
cout << "B::f()" << endl;
}
private:
int _ib;
};
内存布局:
虚函数指针和虚基指针都有一份
2.4、单个虚继承,带虚函数(自己新增虚函数)
class B
: virtual public A
{
public:
B() : _ib(20) {}
void fb()
{
cout << "A::fb()" << endl;
}
virtual
void f()
{
cout << "B::f()" << endl;
}
virtual
void fb2()
{
cout << "B::fb2()" << endl;
}
private:
int _ib;
};
内存布局:
新增了一个虚函数指针,指向新的虚表,新的虚表存放新增的虚函数
这里为什么要新增一个虚函数指针呢?其实这里是典型的空间换时间的做法,如果没有这个新增的虚函数指针,就需要通过虚基指针访问虚基表找到虚基类A的虚函数指针去访问虚表,这里跳转了很多次,运行时间肯定大大增加了,所以增加一个虚函数指针,就可以直接访问到新增的虚函数了
结论:
1.如果派生类没有自己新增的虚函数,此时派生类对象不会产生虚函数指针
2.如果派生类拥有自己新增的虚函数,此时派生类对象就会产生自己本身的虚函数指针(指向新增的虚函数表),并且该虚函数指针位于派生类对象存储空间的开始位置
单个继承带虚函数的总结:
1、类有虚函数,就会有虚函数指针,指向虚函数表,虚函数表存放虚函数的入口地址
2、继承关系为虚继承,派生类中就会有一个虚基指针,虚基指针指向虚基表,虚基表的第一条内容表示的是该虚基指针距离所在的子对象的首地址的偏移,虚基表的第二条内容表示的是该虚基指针距离虚基类子对象的首地址的偏移
3、非虚继承:基类内存布局在前,派生类内存布局在后;
虚继承:派生类布局在前,基类布局在后
4、非虚继承:派生类新增虚函数使用的是基类继承过来的虚函数指针,新增的虚函数入口地址放在继承过来的虚函数表中
虚继承:派生类新增虚函数会产生本身的虚函数指针(并且该虚函数指针位于派生类对象存储空间的开始位置),指向新增的虚函数表,新增的虚函数入口地址放在新增的虚函数表中