- 编程
- cpp
预知识
本文的内容使用的是32位的编译器编译出的结果,可以打印出类的内存布局信息
DevCPP IDE
这个IDE是我比较喜欢的windows下的cpp的IDE之一,它有一个工具->编译选项,可以选择编译器类型,也可以在编译选项中加入一些信息,为了能够输出内存布局信息,我在编译时加入以下命令
--std=c++11 -fdump-class-hierarchy
-fdump-class-hierarchy 这个选项能够在gcc编译时生成类的布局信息,生成的文件名类似为6.cpp.002t.class
vs2017
vs使用的编译器是cl,它的命令为
/d1reportAllClassLayout: 输出所有类相关布局
clang
clang -Xclang -fdump-record-layouts
理解内存布局信息
测试代码如下
class father{
int a;
int b;
};
class child: public father{
};
int main(){
return 0;
}
使用 g++ -fdump-class-hierarchy test.cpp 生成了 test.cpp.002t.class内容如下
Class father
size=8 align=4
base size=8 base align=4
father (0x0x16662d8) 0
Class child
size=8 align=4
base size=8 base align=4
child (0x0x3984e00) 0
father (0x0x1666310) 0
显示了类的大小和对齐信息。
题话外: 理解内存对齐
- 结构体第一个成员的偏移量(offset)为0,以后每个成员相对于结构体首地址的 offset 都是该成员大小与有效对齐值中较小那个的整数倍,如有需要编译器会在成员之间加上填充字节。
- 结构体的总大小为 有效对齐值 的整数倍,如有需要编译器会在最末一个成员之后加上填充字节。
多态与虚表
多态,简单来说,是指在继承层次中,父类的指针可以具有多种形态——当它指向某个子类对象时,通过它能够调用到子类的函数,而非父类的函数。
虚函数指针一般都放在对象内存布局的第一个位置上,这是为了保证在多层继承或多重继承的情况下能以最高效率取到虚函数表。当vprt位于对象内存最前面时,对象的地址即为虚函数指针地址。我们可以取得虚函数指针的地址。
下面的方式取得虚函数指针的地址(而非虚函数指针指向的地址)
Base b(1000);
int * vptrAdree = (int *)(&b);
vptrAdree指向了虚函数指针(指向虚函数表),
而我们可以通过如下的方式获得第一个虚函数的地址
Base b(1000);
using fun=void(*) ();
fun fun1 = (fun)*((int *)*(int *)(&b));
fun fun2 = (fun)*((int *)*(int *)(&c)+1);
简单的情况,单层继承与虚函数全覆盖
通过查看编译器生成的内存布局信息来具体地看:
#include <iostream>
using namespace std;
class Base{
int a;
virtual int print(){
cout<<" i am base"<<endl;
}
virtual int print2(){
cout<<" i am base2" <<endl;
}
};
class child: public Base{
int print(){
cout<< " i am the child" <<endl;
}
int print2(){
cout<<" i am the child2" <<endl;
}
};
int main(){
Base testBase;
//using fun=int(*)();
typedef int(*fun) (void);
fun print=(fun)*((int *)*(int *)(&testBase));
print();
cout<<(int *)*(int *)(&testBase)<<endl;
cout<<((int *)*(int *)(&testBase))+1<<endl;
fun pprint2 = (fun)*((int *)*(int *)(&testBase)+1);
pprint2();
return 0;
}
g++编译后生成的内存布局信息为:
Vtable for Base
Base::_ZTV4Base: 4u entries
0 (int (*)(...))0
4 (int (*)(...))(& _ZTI4Base)
8 (int (*)(...))Base::print
12 (int (*)(...))Base::print2
Class Base
size=8 align=4
base size=8 base align=4
Base (0x0x4e057a8) 0
vptr=((& Base::_ZTV4Base) + 8u)
Vtable for child
child::_ZTV5child: 4u entries
0 (int (*)(...))0
4 (int (*)(...))(& _ZTI5child)
8 (int (*)(...))child::print
12 (int (*)(...))child::print2
Class child
size=8 align=4
base size=8 base align=4
child (0x0x4e4b280) 0
vptr=((& child::_ZTV5child) + 8u)
Base (0x0x4e057e0) 0
primary-for child (0x0x4e4b280)
Vtable表示的是虚函数表,可以发现Vtable for Base里面保存了vptr, 而且位置是vptr=((& Base::_ZTV4Base) + 8u)(注意这里加了8的偏移),这个位置的第一个内容就是(int (*)(...))Base::print。
复杂的情况:多重继承与部分虚函数覆盖
#include <iostream>
using namespace std;
class Parent {
public:
int iparent;
Parent ():iparent (10) {}
virtual void g() { cout << " Parent::g()" << endl; }
virtual void f() { cout << " Parent::f()" << endl; }
virtual void h() { cout << " Parent::h()" << endl; }
};
class Child : public Parent {
public:
int ichild;
Child():ichild(100) {}
virtual void g_child() { cout << "Child::g_child()" << endl; }
virtual void h_child() { cout << "Child::h_child()" << endl; }
virtual void f() { cout << "Child::f()" << endl; }
};
class GrandChild : public Child{
public:
int igrandchild;
GrandChild():igrandchild(1000) {}
virtual void g_child() { cout << "GrandChild::g_child()" << endl; }
virtual void f() { cout << "GrandChild::f()" << endl; }
virtual void h_grandchild() { cout << "GrandChild::h_grandchild()" << endl; }
};
int main(){
typedef void(*Fun)(void);
GrandChild gc;
int** pVtab = (int**)&gc;
cout << "[0] GrandChild::_vptr->" << endl;
for (int i=0; (Fun)pVtab[0][i]!=NULL; i++){
Fun pFun = (Fun)pVtab[0][i];
cout << " ["<<i<<"] ";
pFun();
}
cout << "[1] Parent.iparent = " << (int)pVtab[1] << endl;
cout << "[2] Child.ichild = " << (int)pVtab[2] << endl;
cout << "[3] GrandChild.igrandchild = " << (int)pVtab[3] << endl;
}
内存布局信息如下
Vtable for Parent
Parent::_ZTV6Parent: 5u entries
0 (int (*)(...))0
4 (int (*)(...))(& _ZTI6Parent)
8 (int (*)(...))Parent::g
12 (int (*)(...))Parent::f
16 (int (*)(...))Parent::h
Class Parent
size=8 align=4
base size=8 base align=4
Parent (0x0x4de37a8) 0
vptr=((& Parent::_ZTV6Parent) + 8u)
Vtable for Child
Child::_ZTV5Child: 7u entries
0 (int (*)(...))0
4 (int (*)(...))(& _ZTI5Child)
8 (int (*)(...))Parent::g
12 (int (*)(...))Child::f
16 (int (*)(...))Parent::h
20 (int (*)(...))Child::g_child
24 (int (*)(...))Child::h_child
Class Child
size=12 align=4
base size=12 base align=4
Child (0x0x4e2a5c0) 0
vptr=((& Child::_ZTV5Child) + 8u)
Parent (0x0x4de37e0) 0
primary-for Child (0x0x4e2a5c0)
Vtable for GrandChild
GrandChild::_ZTV10GrandChild: 8u entries
0 (int (*)(...))0
4 (int (*)(...))(& _ZTI10GrandChild)
8 (int (*)(...))Parent::g
12 (int (*)(...))GrandChild::f
16 (int (*)(...))Parent::h
20 (int (*)(...))GrandChild::g_child
24 (int (*)(...))Child::h_child
28 (int (*)(...))GrandChild::h_grandchild
Class GrandChild
size=16 align=4
base size=16 base align=4
GrandChild (0x0x4e2aa80) 0
vptr=((& GrandChild::_ZTV10GrandChild) + 8u)
Child (0x0x4e2aac0) 0
primary-for GrandChild (0x0x4e2aa80)
Parent (0x0x4de3818) 0
primary-for Child (0x0x4e2aac0)
通过查看可以发现,子类的虚函数表里面的优先级是先父类,再子类再孙类,同一优先级按照声明顺序排地址,即使父类的虚函数被覆盖了,也要写在原来的位置,这样能够保证,父类的指针能按照函数名找到那个地址。
程序的执行结果为:
[0] GrandChild::_vptr->
<pre> [0] GrandChild::f()
[1] Parent::g()
[2] Parent::h()
[3] GrandChild::g_child()
[4] Child::h1()
[5] GrandChild::h_grandchild()
[1] Parent.iparent = 10
[2] Child.ichild = 100
[3] GrandChild.igrandchild = 1000
可知
- 虚函数表在最前面的位置。
- 成员变量根据其继承和声明顺序依次放在后面。
- 在单一的继承中,被overwrite的虚函数在虚函数表中得到了更新。
多重继承
#include <iostream>
using namespace std;
class Base1 {
public:
int ibase1;
Base1():ibase1(10) {}
virtual void f() { cout << "Base1::f()" << endl; }
virtual void g() { cout << "Base1::g()" << endl; }
virtual void h() { cout << "Base1::h()" << endl; }
};
class Base2 {
public:
int ibase2;
Base2():ibase2(20) {}
virtual void f() { cout << "Base2::f()" << endl; }
virtual void g() { cout << "Base2::g()" << endl; }
virtual void h() { cout << "Base2::h()" << endl; }
};
class Base3 {
public:
int ibase3;
Base3():ibase3(30) {}
virtual void f() { cout << "Base3::f()" << endl; }
virtual void g() { cout << "Base3::g()" << endl; }
virtual void h() { cout << "Base3::h()" << endl; }
};
class Derive : public Base1, public Base2, public Base3 {
public:
int iderive;
Derive():iderive(100) {}
virtual void f() { cout << "Derive::f()" << endl; }
virtual void g1() { cout << "Derive::g1()" << endl; }
};
int main(){
typedef void(*Fun)(void);
Derive d;
int** pVtab = (int**)&d;
cout << "[0] Base1::_vptr->" << endl;
Fun pFun = (Fun)pVtab[0][0];
cout << " [0] ";
pFun();
pFun = (Fun)pVtab[0][1];
cout << " [1] ";pFun();
pFun = (Fun)pVtab[0][2];
cout << " [2] ";pFun();
pFun = (Fun)pVtab[0][3];
cout << " [3] "; pFun();
pFun = (Fun)pVtab[0][4];
cout << " [4] "; cout<<pFun<<endl;
cout << "[1] Base1.ibase1 = " << (int)pVtab[1] << endl;
int s = sizeof(Base1)/4;
cout << "[" << s << "] Base2::_vptr->"<<endl;
pFun = (Fun)pVtab[s][0];
cout << " [0] "; pFun();
pFun = (Fun)pVtab[s][1];
cout << " [1] "; pFun();
pFun = (Fun)pVtab[s][2];
cout << " [2] "; pFun();
pFun = (Fun)pVtab[s][3];
cout << " [3] ";
cout<<pFun<<endl;
cout << "["<< s+1 <<"] Base2.ibase2 = " << (int)pVtab[s+1] << endl;
s = s + sizeof(Base2)/4;
cout << "[" << s << "] Base3::_vptr->"<<endl;
pFun = (Fun)pVtab[s][0];
cout << " [0] "; pFun();
pFun = (Fun)pVtab[s][1];
cout << " [1] "; pFun();
pFun = (Fun)pVtab[s][2];
cout << " [2] "; pFun();
pFun = (Fun)pVtab[s][3];
cout << " [3] ";
cout<<pFun<<endl;
s++;
cout << "["<< s <<"] Base3.ibase3 = " << (int)pVtab[s] << endl;
s++;
cout << "["<< s <<"] Derive.iderive = " << (int)pVtab[s] << endl;
return 0;
}
内存分布情况如下
Vtable for Base1
Base1::_ZTV5Base1: 5u entries
0 (int (*)(...))0
4 (int (*)(...))(& _ZTI5Base1)
8 (int (*)(...))Base1::f
12 (int (*)(...))Base1::g
16 (int (*)(...))Base1::h
Class Base1
size=8 align=4
base size=8 base align=4
Base1 (0x0x4d907a8) 0
vptr=((& Base1::_ZTV5Base1) + 8u)
Vtable for Base2
Base2::_ZTV5Base2: 5u entries
0 (int (*)(...))0
4 (int (*)(...))(& _ZTI5Base2)
8 (int (*)(...))Base2::f
12 (int (*)(...))Base2::g
16 (int (*)(...))Base2::h
Class Base2
size=8 align=4
base size=8 base align=4
Base2 (0x0x4d907e0) 0
vptr=((& Base2::_ZTV5Base2) + 8u)
Vtable for Base3
Base3::_ZTV5Base3: 5u entries
0 (int (*)(...))0
4 (int (*)(...))(& _ZTI5Base3)
8 (int (*)(...))Base3::f
12 (int (*)(...))Base3::g
16 (int (*)(...))Base3::h
Class Base3
size=8 align=4
base size=8 base align=4
Base3 (0x0x4d90818) 0
vptr=((& Base3::_ZTV5Base3) + 8u)
Vtable for Derive
Derive::_ZTV6Derive: 16u entries
0 (int (*)(...))0
4 (int (*)(...))(& _ZTI6Derive)
8 (int (*)(...))Derive::f
12 (int (*)(...))Base1::g
16 (int (*)(...))Base1::h
20 (int (*)(...))Derive::g1
24 (int (*)(...))-8
28 (int (*)(...))(& _ZTI6Derive)
32 (int (*)(...))Derive::_ZThn8_N6Derive1fEv
36 (int (*)(...))Base2::g
40 (int (*)(...))Base2::h
44 (int (*)(...))-16
48 (int (*)(...))(& _ZTI6Derive)
52 (int (*)(...))Derive::_ZThn16_N6Derive1fEv
56 (int (*)(...))Base3::g
60 (int (*)(...))Base3::h
Class Derive
size=28 align=4
base size=28 base align=4
Derive (0x0x4ddeb40) 0
vptr=((& Derive::_ZTV6Derive) + 8u)
Base1 (0x0x4d90850) 0
primary-for Derive (0x0x4ddeb40)
Base2 (0x0x4d90888) 8
vptr=((& Derive::_ZTV6Derive) + 32u)
Base3 (0x0x4d908c0) 16
vptr=((& Derive::_ZTV6Derive) + 52u)
程序的输出结果为
[0] Base1::_vptr->
[0] Derive::f()
[1] Base1::g()
[2] Base1::h()
[3] Derive::g1()
[4] 1
[1] Base1.ibase1 = 10
[2] Base2::_vptr->
[0] Derive::f()
[1] Base2::g()
[2] Base2::h()
[3] 1
[3] Base2.ibase2 = 20
[4] Base3::_vptr->
[0] Derive::f()
[1] Base3::g()
[2] Base3::h()
[3] 0
[5] Base3.ibase3 = 30
[6] Derive.iderive = 100
结论:
- 每个父类都有自己的虚表。
- 子类的成员函数被放到了第一个父类的表中。
- 内存布局中,其父类布局依次按声明顺序排列。
- 每个父类的虚表中的f()函数都被overwrite成了子类的f()。这样做就是为了解决不同的父类类型的指针指向同一个子类实例,而能够调用到实际的函数。
多重继承
#include <iostream>
using namespace std;
class B
{
public:
int ib;
char cb;
public:
B():ib(0),cb('B') {}
virtual void f() { cout << "B::f()" << endl;}
virtual void Bf() { cout << "B::Bf()" << endl;}
};
class B1 : public B
{
public:
int ib1;
char cb1;
public:
B1():ib1(11),cb1('1') {}
virtual void f() { cout << "B1::f()" << endl;}
virtual void f1() { cout << "B1::f1()" << endl;}
virtual void Bf1() { cout << "B1::Bf1()" << endl;}
};
class B2: public B
{
public:
int ib2;
char cb2;
public:
B2():ib2(12),cb2('2') {}
virtual void f() { cout << "B2::f()" << endl;}
virtual void f2() { cout << "B2::f2()" << endl;}
virtual void Bf2() { cout << "B2::Bf2()" << endl;}
};
class D : public B1, public B2
{
public:
int id;
char cd;
public:
D():id(100),cd('D') {}
virtual void f() { cout << "D::f()" << endl;}
virtual void f1() { cout << "D::f1()" << endl;}
virtual void f2() { cout << "D::f2()" << endl;}
virtual void Df() { cout << "D::Df()" << endl;}
};
int main(){
typedef void(*Fun)(void);
int** pVtab = NULL;
Fun pFun = NULL;
D d;
pVtab = (int**)&d;
cout << "[0] D::B1::_vptr->" << endl;
pFun = (Fun)pVtab[0][0];
cout << " [0] "; pFun();
pFun = (Fun)pVtab[0][1];
cout << " [1] "; pFun();
pFun = (Fun)pVtab[0][2];
cout << " [2] "; pFun();
pFun = (Fun)pVtab[0][3];
cout << " [3] "; pFun();
pFun = (Fun)pVtab[0][4];
cout << " [4] "; pFun();
pFun = (Fun)pVtab[0][5];
cout << " [5] 0x" << pFun << endl;
cout << "[1] B::ib = " << (int)pVtab[1] << endl;
cout << "[2] B::cb = " << static_cast<char>((int)(pVtab[2]))<< endl;
cout << "[3] B1::ib1 = " << (int)pVtab[3] << endl;
cout << "[4] B1::cb1 = " << (char)(int)pVtab[4] << endl;
cout << "[5] D::B2::_vptr->" << endl;
pFun = (Fun)pVtab[5][0];
cout << " [0] "; pFun();
pFun = (Fun)pVtab[5][1];
cout << " [1] "; pFun();
pFun = (Fun)pVtab[5][2];
cout << " [2] "; pFun();
pFun = (Fun)pVtab[5][3];
cout << " [3] "; pFun();
pFun = (Fun)pVtab[5][4];
cout << " [4] 0x" << pFun << endl;
cout << "[6] B::ib = " << (int)pVtab[6] << endl;
cout << "[7] B::cb = " << (char)(int)pVtab[7] << endl;
cout << "[8] B2::ib2 = " << (int)pVtab[8] << endl;
cout << "[9] B2::cb2 = " << (char)(int)pVtab[9] << endl;
cout << "[10] D::id = " << (int)pVtab[10] << endl;
cout << "[11] D::cd = " << (char)(int)pVtab[11] << endl;
return 0;
}
内存分布情况如下
Vtable for B
B::_ZTV1B: 4u entries
0 (int (*)(...))0
4 (int (*)(...))(& _ZTI1B)
8 (int (*)(...))B::f
12 (int (*)(...))B::Bf
Class B
size=12 align=4
base size=9 base align=4
B (0x0x4dc27a8) 0
vptr=((& B::_ZTV1B) + 8u)
Vtable for B1
B1::_ZTV2B1: 6u entries
0 (int (*)(...))0
4 (int (*)(...))(& _ZTI2B1)
8 (int (*)(...))B1::f
12 (int (*)(...))B::Bf
16 (int (*)(...))B1::f1
20 (int (*)(...))B1::Bf1
Class B1
size=20 align=4
base size=17 base align=4
B1 (0x0x4e09780) 0
vptr=((& B1::_ZTV2B1) + 8u)
B (0x0x4dc27e0) 0
primary-for B1 (0x0x4e09780)
Vtable for B2
B2::_ZTV2B2: 6u entries
0 (int (*)(...))0
4 (int (*)(...))(& _ZTI2B2)
8 (int (*)(...))B2::f
12 (int (*)(...))B::Bf
16 (int (*)(...))B2::f2
20 (int (*)(...))B2::Bf2
Class B2
size=20 align=4
base size=17 base align=4
B2 (0x0x4e09c40) 0
vptr=((& B2::_ZTV2B2) + 8u)
B (0x0x4dc2818) 0
primary-for B2 (0x0x4e09c40)
Vtable for D
D::_ZTV1D: 14u entries
0 (int (*)(...))0
4 (int (*)(...))(& _ZTI1D)
8 (int (*)(...))D::f
12 (int (*)(...))B::Bf
16 (int (*)(...))D::f1
20 (int (*)(...))B1::Bf1
24 (int (*)(...))D::f2
28 (int (*)(...))D::Df
32 (int (*)(...))-20
36 (int (*)(...))(& _ZTI1D)
40 (int (*)(...))D::_ZThn20_N1D1fEv
44 (int (*)(...))B::Bf
48 (int (*)(...))D::_ZThn20_N1D2f2Ev
52 (int (*)(...))B2::Bf2
Class D
size=48 align=4
base size=45 base align=4
D (0x0x4e27040) 0
vptr=((& D::_ZTV1D) + 8u)
B1 (0x0x4e27080) 0
primary-for D (0x0x4e27040)
B (0x0x4dc2850) 0
primary-for B1 (0x0x4e27080)
B2 (0x0x4e270c0) 20
vptr=((& D::_ZTV1D) + 40u)
B (0x0x4dc2888) 20
primary-for B2 (0x0x4e270c0)
输出结果为
[0] D::B1::_vptr->
[0] D::f()
[1] B::Bf()
[2] D::f1()
[3] B1::Bf1()
[4] D::f2()
[5] 0x1
[1] B::ib = 0
[2] B::cb = B
[3] B1::ib1 = 11
[4] B1::cb1 = 1
[5] D::B2::_vptr->
[0] D::f()
[1] B::Bf()
[2] D::f2()
[3] B2::Bf2()
[4] 0x0
[6] B::ib = 0
[7] B::cb = B
[8] B2::ib2 = 12
[9] B2::cb2 = 2
[10] D::id = 100
[11] D::cd = D
运行结果为:
发现最顶端的父类B其成员变量和虚函数存在于B1和B2中,并被D给继承下去了。而在D中,其有B1和B2的实例,于是B的成员在D的实例中存在两份,一份是B1继承而来的,另一份是B2继承而来的,因此就会浪费内存。
尽管我们可以通过明确指明调用路径以消除二义性,但二义性的潜在性还没有消除,我们可以通过虚继承来使D类只拥有一个ib实体。
虚继承
虚继承解决了菱形继承中最派生类拥有多个间接父类实例的情况。虚继承的派生类的内存布局与普通继承很多不同,主要体现在:
- 虚继承的子类,如果本身定义了新的虚函数,则编译器为其生成一个虚函数指针(vptr)以及一张虚函数表。该vptr位于对象内存最前面。vs非虚继承:直接扩展父类虚函数表。
- 虚继承的子类也单独保留了父类的vprt与虚函数表。这部分内容接与子类内容以一个四字节的0来分界。
- 虚继承的子类对象中,含有四字节的虚表指针偏移值。
简单虚继承
#include <iostream>
using namespace std;
class B
{
public:
int ib;
public:
B(int i=1) :ib(i){}
virtual void f() { cout << "B::f()" << endl; }
virtual void Bf() { cout << "B::Bf()" << endl; }
};
class B1 : virtual public B
{
public:
int ib1;
public:
B1(int i = 100 ) :ib1(i) {}
virtual void f() { cout << "B1::f()" << endl; }
virtual void f1() { cout << "B1::f1()" << endl; }
virtual void Bf1() { cout << "B1::Bf1()" << endl; }
};
int main(){
B1 b;
using Fun=void(*)();
Fun pFun = NULL;
int ** pvtable = (int**)&b;
for (int i=0; i<9; i++){
if (pvtable[0][i]==NULL) {
cout<<"here:"<<(int)pvtable[0][i]<<endl;
i+=4;
continue;
}
pFun = (Fun)pvtable[0][i];
pFun();
}
cout<<"size == "<<sizeof(b)<<endl;
cout<<"[1]:B1::ib1="<<(int)pvtable[1]<<endl;
cout<<"[3]:B::ib="<<(int)pvtable[3]<<endl;
cout<<"[2]:B::ib="<<(int)pvtable[2]<<endl;
// pFun = (Fun)pvtable[2];
// pFun();
// for (int i=1; pvtable[1][i]!=NULL; i++){
// pFun = (Fun)pvtable[1][i];
// pFun();
// }
return 0;
}
内存布局
Vtable for B
B::_ZTV1B: 4u entries
0 (int (*)(...))0
4 (int (*)(...))(& _ZTI1B)
8 (int (*)(...))B::f
12 (int (*)(...))B::Bf
Class B
size=8 align=4
base size=8 base align=4
B (0x0x4f527a8) 0
vptr=((& B::_ZTV1B) + 8u)
Vtable for B1
B1::_ZTV2B1: 12u entries
0 8u
4 (int (*)(...))0
8 (int (*)(...))(& _ZTI2B1)
12 (int (*)(...))B1::f
16 (int (*)(...))B1::f1
20 (int (*)(...))B1::Bf1
24 0u
28 4294967288u
32 (int (*)(...))-8
36 (int (*)(...))(& _ZTI2B1)
40 (int (*)(...))B1::_ZTv0_n12_N2B11fEv
44 (int (*)(...))B::Bf
VTT for B1
B1::_ZTT2B1: 2u entries
0 ((& B1::_ZTV2B1) + 12u)
4 ((& B1::_ZTV2B1) + 40u)
Class B1
size=16 align=4
base size=8 base align=4
B1 (0x0x4f9a540) 0
vptridx=0u vptr=((& B1::_ZTV2B1) + 12u)
B (0x0x4f527e0) 8 virtual
vptridx=4u vbaseoffset=-12 vptr=((& B1::_ZTV2B1) + 40u)
程序运行结果
B1::f()
B1::f1()
B1::Bf1()
here:0
B::Bf()
size == 16
[1]:B1::ib1=100
[3]:B::ib=1
[2]:B::ib=4781096
这里的内存分布比较奇怪,与vc++的结果不一样。
但是这里size是16 说明有两个指针;
多重虚继承
菱形虚拟继承下,最派生类D类的对象模型又有不同的构成了。在D类对象的内存构成上,有以下几点:
- 在D类对象内存中,基类出现的顺序是:先是B1(最左父类),然后是B2(次左父类),最后是B(虚祖父类)
- D类对象的数据成员id放在B类前面,两部分数据依旧以0来分隔。
- 编译器没有为D类生成一个它自己的vptr,而是覆盖并扩展了最左父类的虚基类表,与简单继承的对象模型相同。
- 超类B的内容放到了D类对象内存布局的最后。