C++虚继承时的内存布局详解
首先
虚继承和虚函数是完全无相关的两个概念。
虚继承是解决C++多重继承问题的一种手段,从不同途径继承来的同一基类,会在子类中存在多份拷贝。这将存在两个问题:其一,浪费存储空间;第二,存在二义性问题,通常可以将派生类对象的地址赋值给基类对象,实现的具体方式是,将基类指针指向继承类(继承类有基类的拷贝)中的基类对象的地址,但是多重继承可能存在一个基类的多份拷贝,这就出现了二义性。
虚继承可以解决多种继承前面提到的两个问题:
虚继承底层实现原理与编译器相关,一般通过虚基类指针和虚基类表实现,每个虚继承的子类都有一个虚基类指针(占用一个指针的存储空间,4字节)和虚基类表(不占用类对象的存储空间)(需要强调的是,虚基类依旧会在子类里面存在拷贝,只是仅仅最多存在一份而已,并不是不在子类里面了);当虚继承的子类被当做父类继承时,虚基类指针也会被继承。
实际上,vbptr指的是虚基类表指针(virtual base table pointer),该指针指向了一个虚基类表(virtual table),虚表中记录了虚基类与本类的偏移地址;通过偏移地址,这样就找到了虚基类成员,而虚继承也不用像普通多继承那样维持着公共基类(虚基类)的两份同样的拷贝,节省了存储空间。
在这里我们可以对比虚函数的实现原理:他们有相似之处,都利用了虚指针(均占用类的存储空间)和虚表(均不占用类的存储空间)。
虚基类依旧存在继承类中,只占用存储空间;虚函数不占用存储空间。
虚基类表存储的是虚基类相对直接继承类的偏移;而虚函数表存储的是虚函数地址。
虚继承的目的是让某个类做出声明,承诺愿意共享它的基类。其中,这个被共享的基类就称为虚基类(Virtual Base Class),
下面来举例说明虚继承时内存的布局:
#include <iostream> using namespace std; class A { protected: int a; }; class B1:public A { protected: int b1; }; class B2:public A { protected: int b2; }; class C:public B1,public B2 { public: int c; }; int main() { cout<<sizeof(A)<<endl; //4 cout<<sizeof(B1)<<endl; //8 cout<<sizeof(B2)<<endl; //8 cout<<sizeof(C)<<endl; //20 return 0; }
这是普通继承,很明显,类 A, B1, B2, C 的 sizeof 为4, 8, 8,20,由于类B1 B2都继承了类A的数据成员,所以再加上自己的数据成员,sizeof就变成了20.
那么,我们再来看看虚继承下的内存情况:
#include <iostream> using namespace std; class A { protected: int a; }; class B1:virtual public A { protected: int b1; }; class B2:virtual public A { protected: int b2; }; class C:public B1,public B2 { public: int c; }; int main() { cout<<sizeof(A)<<endl; //4 cout<<sizeof(B1)<<endl; //12 cout<<sizeof(B2)<<endl; //12 cout<<sizeof(C)<<endl; //24 return 0; }
我们可以看到,菱形继承体系中的子类在内存布局上和普通多继承体系中的子类有很大的不一样。对于类B1和B2,sizeof的值变成了12,除了包含类A的成员变量int a外还多了一个指针vbptr,类C除了继承B1、B2各自的成员变量int b1、int b2,还有共享的int a和自己的成员变量外,还有两个分别属于B、C的指针,所以类C的sizeof就变成了24.
那么类D对象的成员变量布局就便变成了:int a int b1 int b2 vbptr vbptr int c
那么我们还可以加以验证一下:
#include <iostream> using namespace std; class A { protected: //int a; 注释掉a }; class B1:virtual public A { protected: //int b1; 注释掉b1 }; class B2:virtual public A { protected: //int b2; 注释掉b2 }; class C:public B1,public B2 { public: int c; }; int main() { cout<<sizeof(A)<<endl; //1 cout<<sizeof(B1)<<endl; //4 cout<<sizeof(B2)<<endl; //4 cout<<sizeof(C)<<endl; //12 return 0; }
此时在虚继承后,会多出两个vbptr指针,所以就变成了 1 4 4 12.
上一篇: ctf练习
评论列表