关于C++的虚表以及C++对象的内存布局,这两篇blog写的不错:
C++虚函数表解析
http://blog.csdn.net/haoel/archive/2007/12/18/1948051.aspx
C++对象的内存布局(上)
http://blog.csdn.net/haoel/archive/2008/10/15/3081328.aspx
C++对象的内存布局(下)
http://blog.csdn.net/haoel/archive/2008/10/15/3081385.aspx
单重继承虚函数实现多态的机制比较简单,从对象首地址取虚表指针就可以从虚表动态访问到相应的虚函数,但多重继承有多个父类的虚表指针,怎么实现动态绑定呢?
借用上面几篇blog中的例子:
还有一点不太明白,编译器怎么看到这句
Base2 *pb2 = &d;就知道指针要向后偏移呢,编译器并不知道指向的是一个Derive类的对象,根据继承关系吗?不太明白,还请知道的指点一下!
C++虚函数表解析
http://blog.csdn.net/haoel/archive/2007/12/18/1948051.aspx
C++对象的内存布局(上)
http://blog.csdn.net/haoel/archive/2008/10/15/3081328.aspx
C++对象的内存布局(下)
http://blog.csdn.net/haoel/archive/2008/10/15/3081385.aspx
单重继承虚函数实现多态的机制比较简单,从对象首地址取虚表指针就可以从虚表动态访问到相应的虚函数,但多重继承有多个父类的虚表指针,怎么实现动态绑定呢?
借用上面几篇blog中的例子:
- 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; }
- };
- Derive d;
- Base1 *pb1 = &d;
- 00411C36 lea eax,[ebp-0D8h]
- 00411C3C mov dword ptr [ebp-0F0h],eax
- Base2 *pb2 = &d;
- //原来指针赋值时编译器就做了手脚
- 00411C42 lea eax,[ebp-0D8h] ;取对象d的地址
- 00411C48 test eax,eax ;判断地址是否为0
- 00411C4A je main+2EAh (411C5Ah)
- 00411C4C lea ecx,[ebp-0D0h] ;这里地址向后偏移了8
- 00411C52 mov dword ptr [ebp-5B4h],ecx
- 00411C58 jmp main+2F4h (411C64h)
- 00411C5A mov dword ptr [ebp-5B4h],0
- 00411C64 mov edx,dword ptr [ebp-5B4h]
- 00411C6A mov dword ptr [ebp-0FCh],edx ;偏移后的地址送往pb2
- Base3 *pb3 = &d;
- pb1->f(); // Derive::f(),实现了动态绑定
- //看下汇编代码,vs2005
- 00411C9E mov eax,dword ptr [ebp-0F0h] eax:0x0012fe90(d的起始地址)
- 00411CA4 mov edx,dword ptr [eax]
- 00411CA6 mov esi,esp
- 00411CA8 mov ecx,dword ptr [ebp-0F0h]
- 00411CAE mov eax,dword ptr [edx]
- 00411CB0 call eax
- pb2->f(); // Derive::f(),汇编代码跟pb1->f()差不多,那怎么定位到Derive中Base2的虚表呢,发现这里pb2的值已经不再指向d的首地址了,而是偏移了Base1对象的大小,这样就可以找到正确的虚指针了,看下上面的赋值语句,看看编译器是怎么做的
- 00411CB9 mov eax,dword ptr [ebp-0FCh] eax:0x0012fe98(偏移了Base1的大小8)
- 00411CBF mov edx,dword ptr [eax]
- 00411CC1 mov esi,esp
- 00411CC3 mov ecx,dword ptr [ebp-0FCh]
- 00411CC9 mov eax,dword ptr [edx]
- 00411CCB call eax
- pb3->f(); // Derive::f()
- pb1->g(); // Base1::g()
- pb2->g(); // Base1::g()
- pb3->g(); // Base1::g()