C++ 对象的内存布局—— 虚继承下的虚函数
C++ 对象的内存布局(下)这篇文章的“单一虚拟继承”和“钻石型虚拟继承”时的类内存布局讲得不太清楚,我有一处疑问,我用的是VS2005。因此记录一下。
类继承图例如以下:
这里:类B被类B1和B2虚拟继承,而B1和B2同一时候被D继承。
B1的f()、B2的f()覆盖了B的f();
D的f()覆盖了B1的f(),D的f1()覆盖了B1的f1()
D的f()覆盖了B2的f(),D的f2()覆盖了B2的f2()
类代码例如以下:
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 : virtual 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: virtual 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;} };
(1)单一虚拟继承:B1虚拟继承自B
查看类B1的内存布局的代码例如以下:
{ int** pVtab = NULL; Fun pFun = NULL; B1 bb1; pVtab = (int**)&bb1; cout << "sizeof(B1) = " << sizeof(B1) << endl; cout << "[0] B1::_vfptr->" << endl; for (int i = 0; ; i++) { pFun = (Fun)pVtab[0][i]; cout << '\t' << "[" << i << "] 0x" << (int*)pFun << "\t"; if (pFun == NULL) { cout << endl; break; } pFun(); } cout << "[1] B1::_vbptr->" << endl; cout << "\t" << "[0] B1dB1bptrB1 " << pVtab[1][0] << endl; cout << "\t" << "[1] B1dB1bptrB " << pVtab[1][1] << endl; cout << "[2] B1::ib1 = " << (int)pVtab[2] << endl; cout << "[3] B1::cb1 = " << (char)pVtab[3] << endl; cout << "[4] = 0x" << (int*)pVtab[4]/*[0]*/ << endl; cout << "[5] B::_vfptr->" << endl; // cout << '\t' << "[" << 0 << "] thunk = 0x" << (int*)pVtab[5][0] << endl; for (int i = 1; ; i++) { pFun = (Fun)pVtab[5][i]; cout << '\t' << "[" << i << "] 0x" << (int*)pFun << "\t"; if (pFun == NULL) { cout << endl; break; } pFun(); } cout << "[6] B::ib = " << (int)pVtab[6] << endl; cout << "[7] B::ic = " << (char)pVtab[7] << endl; }
代码输出了类B1的布局:
sizeof(B1) = 32
[0] B1::_vfptr->
[0] 0x0041106E B1::f1()
[1] 0x004110AF B1::Bf1()
[2] 0x00000000
[1] B1::_vbptr->
[0] B1dB1bptrB1 -4
[1] B1dB1bptrB 16
[2] B1::ib1 = 11
[3] B1::ic1 = 1
[4] = 0x00000000 //VC++中的一个NULL分隔符把B和B1的布局分开
[5] B::_vfptr->
[0] 0x00411055 //奇怪的地方:这个不是函数B1::f()的地址。由于运行这个地址的函数会出错,那么整个B1类的B1::f()虚函数在哪里呢?难道是“调整块”的地址?
[1] 0x004111F4 B::Bf()
[2] 0x00000000
[6] B::ib = 0
[7] B::ic = B
(2)钻石型虚拟继承
查看类D的内存布局的代码例如以下:
{ int** pVtab = NULL; Fun pFun = NULL; D d; pVtab = (int**)&d; cout << "sizeof(D) = " << sizeof(D) << endl; cout << "[0] D::B1::_vfptr->" << endl; for (int i = 0; ; i++) { pFun = (Fun)pVtab[0][i]; cout << '\t' << "[" << i << "] 0x" << (int*)pFun << "\t"; if (pFun == NULL) { cout << endl; break; } pFun(); } cout << "[1] D::B1::_vbptr->" << endl; cout << "\t" << "[0] DdB1bptrB1 " << pVtab[1][0] << endl; cout << "\t" << "[1] DdB1bptrB " << pVtab[1][1] << endl; cout << "[2] B1::ib1 = " << (int)pVtab[2] << endl; cout << "[3] B1::cb1 = " << (char)pVtab[3] << endl; cout << "[4] D::B2::_vfptr->" << endl; for (int i = 0; ; i++) { pFun = (Fun)pVtab[4][i]; cout << '\t' << "[" << i << "] 0x" << (int*)pFun << "\t"; if (pFun == NULL) { cout << endl; break; } pFun(); } cout << "[5] D::B2::_vbptr->" << endl; cout << "\t" << "[0] DdB2bptrB2 " << pVtab[5][0] << endl; cout << "\t" << "[1] DdB1bptrB " << pVtab[5][1] << endl; cout << "[6] B2::ib2 = " << (int)pVtab[6] << endl; cout << "[7] B2::cb2 = " << (char)pVtab[7] << endl; cout << "[8] D::id = " << (int)pVtab[8] << endl; cout << "[9] D::cd = " << (char)pVtab[9] << endl; cout << "[10] = 0x" << (int*)pVtab[10]/*[0]*/ << endl; cout << "[11] D::B::_vfptr->" << endl; cout << '\t' << "[" << 0 << "] 0x" << (int*)pVtab[11][0] << endl;//奇怪的地方 for (int i = 1; ; i++) { pFun = (Fun)pVtab[11][i]; cout << '\t' << "[" << i << "] 0x" << (int*)pFun << "\t"; if (pFun == NULL) { cout << endl; break; } pFun(); } cout << "[12] B::ib = " << (int)pVtab[12] << endl; cout << "[13] B::ic = " << (char)pVtab[13] << endl; }
代码输出了类D的布局:
sizeof(D) = 56
[0] D::B1::_vfptr->
[0] 0x00411163 D::f1()
[1] 0x004110AF B1::Bf1()
[2] 0x004110D2 D::Df()
[3] 0x00000000
[1] D::B1::_vbptr->
[0] DdB1bptrB1 -4
[1] DdB1bptrB 40
[2] B1::ib1 = 11
[3] B1::cb1 = 1
[4] D::B2::_vfptr->
[0] 0x0041101E D::f2()
[1] 0x00411159 B2::Bf2()
[2] 0x00000000
[5] D::B2::_vbptr->
[0] DdB2bptrB2 -4
[1] DdB1bptrB 24
[6] B2::ib2 = 12
[7] B2::cb2 = 2
[8] D::id = 100
[9] D::cd = D
[10] = 0x00000000 //VC++中的一个NULL分隔符把B和B1和B2的布局分开
[11] D::B::_vfptr->
[0] 0x004110C3 //奇怪的地方:这个不是函数D::f()的地址。由于运行这个地址的函数会出错,那么整个D类的D::f()虚函数在哪里呢?难道是“调整块”的地址?
[1] 0x004111F4 B::Bf()
[2] 0x00000000
[12] B::ib = 0
[13] B::cb = B
眼下我还不清楚是怎么回事,不知道是不是thunk之类的原因。