[C++] 虚函数对C++对象内存模型的影响
测试环境
平台:32位
编译环境:VS2008
虚函数相关背景
虚函数的作用主要是实现了多态机制,即用父类型别的指针指向其派生类的实例,然后通过父类的指针调用派生类的成员函数,这种技术可以让父类的指针有“多种形态”。
问题:从一个例子开始
假设有类继承关系如下:
问题:类Base、MyClass的大小?
结果:4和8?我们写个程序看看结果先!
1 #include <iostream> 2 using namespace std; 3 4 class Base 5 { 6 public : 7 int a_ ; 8 virtual void FuncA() 9 { 10 cout << "Base::FuncA ..." << endl; 11 } 12 13 virtual void FuncB() 14 { 15 cout << "Base::FuncB ..." << endl; 16 } 17 }; 18 19 class MyClass : public Base 20 { 21 public : 22 int b_ ; 23 virtual void FuncB() 24 { 25 cout << "MyClass::FuncB ..." << endl; 26 } 27 28 virtual void FuncC() 29 { 30 cout << "MyClass::FuncC ..." << endl; 31 } 32 }; 33 34 int main (void) 35 { 36 cout << "sizeof(Base) = " << sizeof(Base) << endl; 37 cout << "sizeof(MyClass) = " << sizeof(MyClass) << endl; 38 39 return 0; 40 }
程序运行结果:
分析:Base类对象的内存模型
这两个类对象的大小鬼使神差地增大了4个字节。为什么呢?其实是虚表指针搞的鬼,且看Base类对象的内存模型图。
原来,由于Base类中存在虚函数,所以在Base类对象的前4个字节中存放了一个vptr(虚表指针),该指针指向了一张vtbl(虚表),虚表中存放的就是该类中相关虚函数的入口地址,存放顺序同定义顺序。通过上图我们可以看出Base类对象的虚表中存放了Base::FuncA和Base::FuncB的入口地址。下面我们通过一个程序来验证下:
1 #include <iostream> 2 using namespace std; 3 4 class Base 5 { 6 public : 7 int a_ ; 8 virtual void FuncA() 9 { 10 cout << "Base::FuncA ..." << endl; 11 } 12 13 virtual void FuncB() 14 { 15 cout << "Base::FuncB ..." << endl; 16 } 17 }; 18 19 class MyClass : public Base 20 { 21 public : 22 int b_ ; 23 virtual void FuncB() 24 { 25 cout << "MyClass::FuncB ..." << endl; 26 } 27 28 virtual void FuncC() 29 { 30 cout << "MyClass::FuncC ..." << endl; 31 } 32 }; 33 34 typedef void (*FUNC)(); // 为什么不是 typedef void (*FUNC)(Base *); 35 36 int main (void) 37 { 38 Base ob; 39 40 long **p = (long **)&ob; // 指向虚表指针的指针 41 FUNC fa = (FUNC)p[0][0]; 42 FUNC fb = (FUNC)p[0][1]; 43 44 fa(); 45 fb(); 46 47 return 0; 48 }
程序运行结果:
通过将虚表中的2个函数入口地址赋给2个函数指针变量,通过调用这两个函数指针所指向的函数以及输出信息,我们可以确定虚表中存放的确实是Base::FuncA和Base::FuncB函数的入口地址。
分析:MyClass类对象的内存模型
同样,MyClass类对象的内存模型图如下:
虚表中的顺序:
- 基类中的虚函数优先,由于MyClass中没有实现FuncA,因此保留了Base::FuncA。
- MyClass中定义了FuncB,因此MyClass::FuncB覆盖了Base::FuncB。
- MyClass中定义的FuncC。
下面我们通过程序来验证下:
1 #include <iostream> 2 using namespace std; 3 4 class Base 5 { 6 public : 7 int a_ ; 8 9 virtual void FuncA() 10 { 11 cout << "Base::FunA ..." << endl; 12 } 13 14 virtual void FuncB() 15 { 16 cout << "Base::FunB ..." << endl; 17 } 18 }; 19 20 class MyClass : public Base 21 { 22 public : 23 int b_ ; 24 25 virtual void FuncB() 26 { 27 cout << "MyClass::FuncB ..." << endl; 28 } 29 30 virtual void FuncC() 31 { 32 cout << "MyClass::FuncC ..." << endl; 33 } 34 }; 35 36 typedef void (*FUNC)(); // 为什么不是typedef void (*FUNC)(MyClass *); 37 38 int main (void) 39 { 40 MyClass omy; 41 42 long **p = (long **)&omy; // 指向虚表指针的指针 43 FUNC fa = (FUNC)p[0][0]; 44 FUNC fb = (FUNC)p[0][1]; 45 FUNC fc = (FUNC)p[0][2]; 46 47 fa(); 48 fb(); 49 fc(); 50 51 return 0; 52 }
程序运行结果:
(完)