C++ 对象内存布局和多态实现原理
2017-08-23 17:57
711 查看
进入主题前,先把工具设置好。本文使用编译测试环境:Visual Studio 2013
VS2013查看类内存布局设置方式如下截图:
先选择左侧的C/C++->命令行,然后在其他选项这里写上/d1 reportAllClassLayout,它可以看到所有相关类的内存布局,如果写上/d1 reportSingleClassLayoutXXX(XXX为类名),则只会打出指定类XXX的内存布局。
进入主题:从最基础的class到继承、多重继承、虚拟继承,我们依次展开来看。
1.空类
定义一个空类,如下:
发现空类Base的size是1,原因:C++标准规定,编译器为空类插入1字节的char,以使该类对象在内存得以配置一个地址。
2.普通类(只有nonstatic的成员变量)
定义如下:
这里暂不考虑字节对齐的影响,所以成员变量都设为int型。从这里可以看到普通类的布局方式,nonstatic成员变量依据声明的顺序进行排列(类内偏移为0开始)。
当涉及字符对齐时的内存布局图。
定义如下:
从上图红色框图中,看到char b会被4byte对齐,Base object size是8,而不是5。
当在继承体系中,父类和子类都涉及字节对齐时,我们来看下内存布局状况。
定义如下:
从上图红色框图中,看到Base suboject char b和Derived char d 都被4byte对齐,Derived object size是16。int c的内存并不是从位置5开始,而是从8开始。在继承中char b依然会如Base对象中被4byte对齐处理。这是因为:继承关系中派生类会保持基类subobject的内存布局完整性。
3.普通类(加上static成员和普通function)
定义如下:
可以看出:static成员变量、static成员函数,普通成员函数,都不会占用对象的空间。static成员属于class,所有object共用一份。
4.普通类(加上虚函数)
定义如下:
这个结构图分成上下两部分,上面是内存分布,下面是虚函数表vftable(也称作vtbl)。我们看到class Base size现在是12,除去两个int变量占用8个Byte,虚函数表指针vfptr(也称作vptr)占用4个Byte(32bit 机器指针size是4Byte)。VS2013编译器是把vfptr放在了内存的开始处(0地址偏移),然后再是两个依次声明的int型成员变量;下面生成了虚函数表,紧跟在&Base_meta后面的0表示,指向这张虚函数表的虚函数表指针vfptr在内存中的位置,紧接着下面列出了虚函数,左侧的0是这个虚函数的序号,这里只有一个虚函数,所以只有一项,如果有多个虚函数,会有序号为1,为2的虚函数依次列出来。
5.[b]单一的一般继承(只有nonstatic成员变量)[/b]
定义如下:
单一的一般继承内存布局很简单,继承关系中派生类保持基类subobject的内存布局完整性,Derived的成员变量c & d 紧接在Base subobject之后,并按照声明顺序依次排列布局。
6.单一的一般继承(Derived带虚函数)
定义如下:
从上图看到,Derived带虚函数后,Derived object内存布局起始位置被编译器安插一个vfptr(指向vftable),vftable存储的是虚函数地址。vfptr之后的内存布局和单一的一般继承(Derived不带虚函数)一致。
7.单一的一般继承(带成员变量、虚函数、虚函数覆盖)
定义如下:
Derived的内存布局:
这个例子中,Base含有virtual f() 和 virtual g(),Derived覆写了Base的virtual g(),继承了Base的virtual h(),增加了virtual h_derived()。
我们先看Base的内存布局:vfptr在Base内存offset 0位置,紧接着是两个int行变量,vftable中就是Base定义的两个virtual函数g()和h()的地址。
再来分析Derived的内存布局:从上下两张内部布局图中(Base和Derived)可以看出,继承关系中派生类会保持基类subobject的内存布局完整性(Derived内存布局图中红色框内),vfptr还是处于内部布局offset 0位置(也是base class的内部布局offset 0位置),Derived中并没有额外再生成新的vfptr。但此时vfptr指向的的vftable中有三个虚函数,序号0是Derived中的覆写g(),序号1是继承自Base的虚函数h(),序号2是Derived中的新增virtual
h_derived()。
编译器是如何利用vfptr与vftable来实现多态的呢?当创建一个含有虚函数的父类的对象时,编译器在对象构造时将vfptr指向父类的vftable;同样,当创建子类的对象时,编译器在构造函数里将vfptr指向子类的vftable(这个虚表里面的虚函数入口地址是子类的,包含有:继承来父类的虚函数,子类中覆写父类的虚函数,子类中扩展新增的虚函数)。所以,如果是调用Base *p = new Derived();父类指针p指向的内存起始位置是子类对象的内存起始位置,但父类指针只能访问父类内存范围内的成员。在new
Derived()时,子类对象的vfptr指向的是子类的vftable,所以这时候通过父类指针p访问的虚函数其实是子类中的vftable中的虚函数,p->VirtualFunction,实际上是p->vfptr[序号]->VirtualFunction,这就是多态的实现原理。
8.单一的虚拟继承(带成员变量、虚函数、虚函数覆盖)
定义如下:
从上图看到,虚拟继承,Derived object的内存起始位置,被编译器安插一个vbptr,vbptr是Derived中指向虚基类表(vbtable)的指针。
上图Derived::$vtable@看到序号1对应的数字12,是virtual base Base距离vbptr的偏移距离,也正是上图中virtual base Base下方vfptr在内存布局中的位置12。
9.单一的虚拟继承(带成员变量、虚函数、虚函数覆盖,派生类新增虚函数)
定义如下:
从上图与单一的虚拟继承(派生类不新增虚函数)相比,Derived object size是28,相比增大了4个byte,而这4个byte正是Derived中被编译器新增安插的vfptr(上图红色框图中)。我们看到Derived新增的虚函数h_derived并没有直接放入Derived::$vftable@Base@中,而是新增加vftable(Derived::$vftable@Derived@)。
10.多重继承
定义如下:
Base中有一个vfptr,地址偏移为0。
Derived1继承了Base,内存布局是先父类后子类。
Derived2内部布局和Derived1相同:继承了Base,内存布局是先父类后子类。
重点看这个类Derived,它按照继承顺序并列排布着继承而来的两个父类Derived1与Derived2,还有自身的成员变量e。Derived1包含了继承自父类的Base(Base有一个0地址偏移的vfptr,然后是成员变量a和b)以及它自身的成员变量c。Derived2的内存布局类似于Derived1。我们注意到Derived2里面也有一份Base,这就是C++多重继承中的二义性。
此外,我们看到这里有两份vftable,分别针对Derived1与Derived2。我们把上图中的关于vftable部分截取出来,如下图,分析下这两个vftable。
在靠上面的vftable@Derived1的&Derived_meta下方的数字0,表示的是指向这张虚函数表的vfptr在Derived内存布局中的偏移位置,这也正是Derived1中的vfptr在Derived的内存偏移位置。靠下面的vftable@Derived2的那个16表示指向这个vftable的vfptr在内存中的偏移,这也正是Derived2中的vfptr在Derived的内存偏移位置。
11.多重继承(派生类新增虚函数)
同8中的内定义相比,只是在派生类Derived新增虚函数:virtual void virtualDerivedFunction()
定义如下:
此case中派生类Derived和8中所讲的内存布局相比,我们看到唯一差异点:上图红色框图中,Derived新增的虚函数只有在vftable@Derived1的虚函数表中有,并不会在vftable@Derived2的虚函数表。也就是说Derived并没有额外新增vfptr和vftable,而是直接引用来自继承列表中的第一个父类Derived1中的vftable。
12.钻石型多重虚继承
为了解决多重继承可能带来的二义性,以及减少对基类的重复。C++从语言层面提供了虚继承,其代价是增加了虚表指针的负担(更多的虚表指针)。还是直接看例子。
定义如下:
从上图看出父类Base内存布局没有发生变化。但往下看:
从上图中,我们看到Derived1的内存布局已经发生改变,相比原来非虚继承:非虚继承内部布局中是先排vfptr(vfptr位于0地址偏移处)与Base成员变量,再次接着才是Derived1的成员变量c。但现在有3个虚指针了,其中2个vfptr,另一个是vbptr,vbptr是Derived1中指向虚基类表(vbtable)的指针。
此外对比非虚继承,我们看到:如果是虚继承,则子类会新增一个指向vbtable的vbptr。
我们按照下图依次分析这3张虚表。
第1张表(Derived1::$vftable@Derived1@)是vfptr指向的vftable,&Derived1_meta下方的0表示vfptr在Derived1中的内存位置,紧接着下面的序号0对应的是Derived1新增的虚函数(virtual void virtualDerived1Function())的地址。
第2张表(Derived1::$vbtable@)是虚基类表vbtable,标号0对应的4表示vbptr在Derived1中的内存位置,紧接着下方标号1对应的8(Derived1d(Derived1+4)Base)是vbptr距离virtual base Base的偏移距离(也就是virtual base Base在Derived1中的内存位置12减去vbptr在Derived1中内存位置4)。
第3张表(Derived1::$vftable@Base@)是继承自Base的subobject中vfptr指向的vftable,12表示指向此vftable的vfptr在Derived1中的内存位置,紧接着的标号0对应的表示Derived1中覆写Base的虚函数的地址。
Derived2的内存布局类似于Derived1,同样会有3个虚指针,分别指向3张虚表(第二张是虚基类表)。内部布局如下图:
下面来看最复杂的Derived的内存分布,这里面有5个虚指针了,但Base却只有一份。
第1张(Derived::$vftable@Derived1@)vftable是内含Derived1的,&Derived_meta下方的0表示它的vfptr在Derived内存布局中的位置。
第2张(Derived::$vftable@Derived2@)vftable是内含Derived2的,12表示它的vfptr在Derived内存布局中的位置。
第3张(Derived::$vbtable@Derived1@)vbtable是内含Derived1的,标号0对应的4表示vbptr在Derived中的内存位置,标号1对应的24 (Derivedd(Derived1+4)Base)是base
class Derived1中的vbptr距离virtual base Base的偏移距离(正是下图中virtual base Base在Derived中的内存位置28减去base class Derived1中的vbptr在Derived中的内存位置4)。
第4张(Derived::$vbtable@Derived2@)vbtable是内含Derived2的,标号1对应的12 (Derivedd(Derived2+4)Base)是base class Derived2中的vbptr距离virtual base Base的偏移距离(正是下图中virtual base Base在Derived中的内存位置28减去base class Derived2中的vbptr在Derived中的内存位置16)。
第5张(Derived::$vftable@Base@)vftable是Derived继承自Base的。28表示virtual class Base的vfptr在Derived内存布局中的位置。
最后总结:
1.当类含有虚函数时(包括继承而来的虚函数)都有虚函数表指针vfptr和虚函数表vftable。
2.单一的普通继承(非虚继承),子类只有一个vfptr(子类从父类继承下来),并指向自身的vftable(包含:继承自父类的虚函数、子类覆盖父类的虚函数、子类新增的虚函数)。
3.多重继承(非虚继承),可能存在多个的基类vfptr与vftable。如果子类有新增虚函数,编译器并不会为子类额外新增自己的vfptr,而是直接引用来自继承列表中的第一个父类中的vfptr,并在此vfptr指向的vftable中增加子类新增的虚函数。
4.如果是单一的虚拟继承,则子类对象中会被编译器安插一个指向vbtable(记录virtual base class在内存布局中与vbptr的距离偏移)的vbptr。当子类中有新增虚函数时,编译器则会给子类对象中再新增一个vfptr,这个vfptr指向新增vftable(这张vftable只含有子类自己新增的虚函数),而subobject中的vfptr指向的vftable(也就是virtual
base class中的vfptr指向的vftable)中存储的是子类从父类继承或子类覆盖父类的虚函数地址。
5.对于钻石型多重继承,可以按照以上原则类推。
说明:C++对象内存布局,不同编译器实现细节不同,本文所有测试结果都依赖VS2013。
VS2013查看类内存布局设置方式如下截图:
先选择左侧的C/C++->命令行,然后在其他选项这里写上/d1 reportAllClassLayout,它可以看到所有相关类的内存布局,如果写上/d1 reportSingleClassLayoutXXX(XXX为类名),则只会打出指定类XXX的内存布局。
进入主题:从最基础的class到继承、多重继承、虚拟继承,我们依次展开来看。
1.空类
定义一个空类,如下:
class Base { };编译一下,可以看到输出框里面的布局:
发现空类Base的size是1,原因:C++标准规定,编译器为空类插入1字节的char,以使该类对象在内存得以配置一个地址。
2.普通类(只有nonstatic的成员变量)
定义如下:
class Base { private: int a; int b; };内存布局:
这里暂不考虑字节对齐的影响,所以成员变量都设为int型。从这里可以看到普通类的布局方式,nonstatic成员变量依据声明的顺序进行排列(类内偏移为0开始)。
当涉及字符对齐时的内存布局图。
定义如下:
class Base { private: int a; char b; };内存布局:
从上图红色框图中,看到char b会被4byte对齐,Base object size是8,而不是5。
当在继承体系中,父类和子类都涉及字节对齐时,我们来看下内存布局状况。
定义如下:
class Base { private: int a; char b; };内存布局:
class Derived : public Base{
private:
int c;
char d;
};
从上图红色框图中,看到Base suboject char b和Derived char d 都被4byte对齐,Derived object size是16。int c的内存并不是从位置5开始,而是从8开始。在继承中char b依然会如Base对象中被4byte对齐处理。这是因为:继承关系中派生类会保持基类subobject的内存布局完整性。
3.普通类(加上static成员和普通function)
定义如下:
class Base { public: void f(); static void g(); private: int a; static int c; int b; };内存布局:
可以看出:static成员变量、static成员函数,普通成员函数,都不会占用对象的空间。static成员属于class,所有object共用一份。
4.普通类(加上虚函数)
定义如下:
class Base { public: void f(); virtual void g(); private: int a; int b; };内存布局:
这个结构图分成上下两部分,上面是内存分布,下面是虚函数表vftable(也称作vtbl)。我们看到class Base size现在是12,除去两个int变量占用8个Byte,虚函数表指针vfptr(也称作vptr)占用4个Byte(32bit 机器指针size是4Byte)。VS2013编译器是把vfptr放在了内存的开始处(0地址偏移),然后再是两个依次声明的int型成员变量;下面生成了虚函数表,紧跟在&Base_meta后面的0表示,指向这张虚函数表的虚函数表指针vfptr在内存中的位置,紧接着下面列出了虚函数,左侧的0是这个虚函数的序号,这里只有一个虚函数,所以只有一项,如果有多个虚函数,会有序号为1,为2的虚函数依次列出来。
5.[b]单一的一般继承(只有nonstatic成员变量)[/b]
定义如下:
class Base { private: int a; int b; };内存布局:
class Derived : public Base {
private:
int c;
int d;
};
单一的一般继承内存布局很简单,继承关系中派生类保持基类subobject的内存布局完整性,Derived的成员变量c & d 紧接在Base subobject之后,并按照声明顺序依次排列布局。
6.单一的一般继承(Derived带虚函数)
定义如下:
class Base { private: int a; int b; };内部布局:
class Derived : public Base {
public:
virtual void f();
private:
int c;
int d;
};
从上图看到,Derived带虚函数后,Derived object内存布局起始位置被编译器安插一个vfptr(指向vftable),vftable存储的是虚函数地址。vfptr之后的内存布局和单一的一般继承(Derived不带虚函数)一致。
7.单一的一般继承(带成员变量、虚函数、虚函数覆盖)
定义如下:
class Base { public: void f(); virtual void g(); virtual void h(); private: int a; int b; }; class Derived : public Base { public: virtual void g(); virtual void h_derived(); private: int c; int d; };Base的内存布局:
Derived的内存布局:
这个例子中,Base含有virtual f() 和 virtual g(),Derived覆写了Base的virtual g(),继承了Base的virtual h(),增加了virtual h_derived()。
我们先看Base的内存布局:vfptr在Base内存offset 0位置,紧接着是两个int行变量,vftable中就是Base定义的两个virtual函数g()和h()的地址。
再来分析Derived的内存布局:从上下两张内部布局图中(Base和Derived)可以看出,继承关系中派生类会保持基类subobject的内存布局完整性(Derived内存布局图中红色框内),vfptr还是处于内部布局offset 0位置(也是base class的内部布局offset 0位置),Derived中并没有额外再生成新的vfptr。但此时vfptr指向的的vftable中有三个虚函数,序号0是Derived中的覆写g(),序号1是继承自Base的虚函数h(),序号2是Derived中的新增virtual
h_derived()。
编译器是如何利用vfptr与vftable来实现多态的呢?当创建一个含有虚函数的父类的对象时,编译器在对象构造时将vfptr指向父类的vftable;同样,当创建子类的对象时,编译器在构造函数里将vfptr指向子类的vftable(这个虚表里面的虚函数入口地址是子类的,包含有:继承来父类的虚函数,子类中覆写父类的虚函数,子类中扩展新增的虚函数)。所以,如果是调用Base *p = new Derived();父类指针p指向的内存起始位置是子类对象的内存起始位置,但父类指针只能访问父类内存范围内的成员。在new
Derived()时,子类对象的vfptr指向的是子类的vftable,所以这时候通过父类指针p访问的虚函数其实是子类中的vftable中的虚函数,p->VirtualFunction,实际上是p->vfptr[序号]->VirtualFunction,这就是多态的实现原理。
8.单一的虚拟继承(带成员变量、虚函数、虚函数覆盖)
定义如下:
class Base { public: void f(); virtual void g(); virtual void h(); private: int a; int b; }; class Derived : virtual public Base { public: virtual void g(); private: int c; int d; };内存分布:
从上图看到,虚拟继承,Derived object的内存起始位置,被编译器安插一个vbptr,vbptr是Derived中指向虚基类表(vbtable)的指针。
上图Derived::$vtable@看到序号1对应的数字12,是virtual base Base距离vbptr的偏移距离,也正是上图中virtual base Base下方vfptr在内存布局中的位置12。
9.单一的虚拟继承(带成员变量、虚函数、虚函数覆盖,派生类新增虚函数)
定义如下:
class Base { public: void f(); virtual void g(); virtual void h(); private: int a; int b; }; class Derived : virtual public Base { public: virtual void g(); virtual void h_derived(); private: int c; int d; };内存分布:
从上图与单一的虚拟继承(派生类不新增虚函数)相比,Derived object size是28,相比增大了4个byte,而这4个byte正是Derived中被编译器新增安插的vfptr(上图红色框图中)。我们看到Derived新增的虚函数h_derived并没有直接放入Derived::$vftable@Base@中,而是新增加vftable(Derived::$vftable@Derived@)。
10.多重继承
定义如下:
class Base { public: virtual void virtualFunction(); private: int a; int b; }; class Derived1 : public Base { public: virtual void virtualFunction(); virtual void virtualDerived1Function(); private: int c; }; class Derived2 : public Base { public: virtual void virtualFunction(); virtual void virtualDerived2Function(); private: int d; }; class Derived : public Derived1, public Derived2 { public: virtual void virtualFunction(); private: int e; };内存布局(从父类到子类依次来看):
Base中有一个vfptr,地址偏移为0。
Derived1继承了Base,内存布局是先父类后子类。
Derived2内部布局和Derived1相同:继承了Base,内存布局是先父类后子类。
重点看这个类Derived,它按照继承顺序并列排布着继承而来的两个父类Derived1与Derived2,还有自身的成员变量e。Derived1包含了继承自父类的Base(Base有一个0地址偏移的vfptr,然后是成员变量a和b)以及它自身的成员变量c。Derived2的内存布局类似于Derived1。我们注意到Derived2里面也有一份Base,这就是C++多重继承中的二义性。
此外,我们看到这里有两份vftable,分别针对Derived1与Derived2。我们把上图中的关于vftable部分截取出来,如下图,分析下这两个vftable。
在靠上面的vftable@Derived1的&Derived_meta下方的数字0,表示的是指向这张虚函数表的vfptr在Derived内存布局中的偏移位置,这也正是Derived1中的vfptr在Derived的内存偏移位置。靠下面的vftable@Derived2的那个16表示指向这个vftable的vfptr在内存中的偏移,这也正是Derived2中的vfptr在Derived的内存偏移位置。
11.多重继承(派生类新增虚函数)
同8中的内定义相比,只是在派生类Derived新增虚函数:virtual void virtualDerivedFunction()
定义如下:
class Base { public: virtual void virtualFunction(); private: int a; int b; }; class Derived1 : public Base { public: virtual void virtualFunction(); virtual void virtualDerived1Function(); private: int c; }; class Derived2 : public Base { public: virtual void virtualFunction(); virtual void virtualDerived2Function(); private: int d; }; class Derived : public Derived1, public Derived2 { public: virtual void virtualFunction(); virtual void virtualDerivedFunction(); private: int e; };内存布局:
此case中派生类Derived和8中所讲的内存布局相比,我们看到唯一差异点:上图红色框图中,Derived新增的虚函数只有在vftable@Derived1的虚函数表中有,并不会在vftable@Derived2的虚函数表。也就是说Derived并没有额外新增vfptr和vftable,而是直接引用来自继承列表中的第一个父类Derived1中的vftable。
12.钻石型多重虚继承
为了解决多重继承可能带来的二义性,以及减少对基类的重复。C++从语言层面提供了虚继承,其代价是增加了虚表指针的负担(更多的虚表指针)。还是直接看例子。
定义如下:
class Base { public: virtual void virtualFunction(); private: int a; int b; }; class Derived1 : virtual public Base { public: virtual void virtualFunction(); virtual void virtualDerived1Function(); private: int c; }; class Derived2 : virtual public Base { public: virtual void virtualFunction(); virtual void virtualDerived2Function(); private: int d; }; class Derived : public Derived1, public Derived2 { public: virtual void virtualFunction(); virtual void virtualDerivedFunction(); private: int e; };内存分布(从父类到子类依次来看):
从上图看出父类Base内存布局没有发生变化。但往下看:
从上图中,我们看到Derived1的内存布局已经发生改变,相比原来非虚继承:非虚继承内部布局中是先排vfptr(vfptr位于0地址偏移处)与Base成员变量,再次接着才是Derived1的成员变量c。但现在有3个虚指针了,其中2个vfptr,另一个是vbptr,vbptr是Derived1中指向虚基类表(vbtable)的指针。
此外对比非虚继承,我们看到:如果是虚继承,则子类会新增一个指向vbtable的vbptr。
我们按照下图依次分析这3张虚表。
第1张表(Derived1::$vftable@Derived1@)是vfptr指向的vftable,&Derived1_meta下方的0表示vfptr在Derived1中的内存位置,紧接着下面的序号0对应的是Derived1新增的虚函数(virtual void virtualDerived1Function())的地址。
第2张表(Derived1::$vbtable@)是虚基类表vbtable,标号0对应的4表示vbptr在Derived1中的内存位置,紧接着下方标号1对应的8(Derived1d(Derived1+4)Base)是vbptr距离virtual base Base的偏移距离(也就是virtual base Base在Derived1中的内存位置12减去vbptr在Derived1中内存位置4)。
第3张表(Derived1::$vftable@Base@)是继承自Base的subobject中vfptr指向的vftable,12表示指向此vftable的vfptr在Derived1中的内存位置,紧接着的标号0对应的表示Derived1中覆写Base的虚函数的地址。
Derived2的内存布局类似于Derived1,同样会有3个虚指针,分别指向3张虚表(第二张是虚基类表)。内部布局如下图:
下面来看最复杂的Derived的内存分布,这里面有5个虚指针了,但Base却只有一份。
第1张(Derived::$vftable@Derived1@)vftable是内含Derived1的,&Derived_meta下方的0表示它的vfptr在Derived内存布局中的位置。
第2张(Derived::$vftable@Derived2@)vftable是内含Derived2的,12表示它的vfptr在Derived内存布局中的位置。
第3张(Derived::$vbtable@Derived1@)vbtable是内含Derived1的,标号0对应的4表示vbptr在Derived中的内存位置,标号1对应的24 (Derivedd(Derived1+4)Base)是base
class Derived1中的vbptr距离virtual base Base的偏移距离(正是下图中virtual base Base在Derived中的内存位置28减去base class Derived1中的vbptr在Derived中的内存位置4)。
第4张(Derived::$vbtable@Derived2@)vbtable是内含Derived2的,标号1对应的12 (Derivedd(Derived2+4)Base)是base class Derived2中的vbptr距离virtual base Base的偏移距离(正是下图中virtual base Base在Derived中的内存位置28减去base class Derived2中的vbptr在Derived中的内存位置16)。
第5张(Derived::$vftable@Base@)vftable是Derived继承自Base的。28表示virtual class Base的vfptr在Derived内存布局中的位置。
最后总结:
1.当类含有虚函数时(包括继承而来的虚函数)都有虚函数表指针vfptr和虚函数表vftable。
2.单一的普通继承(非虚继承),子类只有一个vfptr(子类从父类继承下来),并指向自身的vftable(包含:继承自父类的虚函数、子类覆盖父类的虚函数、子类新增的虚函数)。
3.多重继承(非虚继承),可能存在多个的基类vfptr与vftable。如果子类有新增虚函数,编译器并不会为子类额外新增自己的vfptr,而是直接引用来自继承列表中的第一个父类中的vfptr,并在此vfptr指向的vftable中增加子类新增的虚函数。
4.如果是单一的虚拟继承,则子类对象中会被编译器安插一个指向vbtable(记录virtual base class在内存布局中与vbptr的距离偏移)的vbptr。当子类中有新增虚函数时,编译器则会给子类对象中再新增一个vfptr,这个vfptr指向新增vftable(这张vftable只含有子类自己新增的虚函数),而subobject中的vfptr指向的vftable(也就是virtual
base class中的vfptr指向的vftable)中存储的是子类从父类继承或子类覆盖父类的虚函数地址。
5.对于钻石型多重继承,可以按照以上原则类推。
说明:C++对象内存布局,不同编译器实现细节不同,本文所有测试结果都依赖VS2013。
相关文章推荐
- C++对象布局及多态实现探索之内存布局
- C++对象布局及多态实现探索之内存布局
- C++对象布局及多态实现探索之内存布局(转载)
- C++对象布局及多态实现探索之内存布局
- C++对象布局及多态实现探索之内存布局
- C++对象布局及多态实现探索之内存布局(整理的很多链接)
- C++对象及多态实现探索之内存布局
- 潘凯:C++对象布局及多态实现的探索(八)
- (转载)【C++拾遗】 从内存布局看C++虚继承的实现原理
- 【C++拾遗】 从内存布局看C++虚继承的实现原理
- 潘凯:C++对象布局及多态实现的探索(九)
- 从内存布局看C++虚继承的实现原理
- C++对象布局及多态实现探索之虚函数调用
- C++对象布局及多态实现的探索(三)
- 潘凯:C++对象布局及多态实现的探索(十)
- c++多态情况下对象内存布局
- C++对象布局及多态实现的探索(四)
- 潘凯:C++对象布局及多态实现的探索(十一)
- C++对象布局及多态实现之带虚函数的类
- C++对象布局及多态实现之带虚函数的类