[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 }

     程序运行结果:

 

(完)

posted @ 2014-03-10 22:59  helloamigo  阅读(783)  评论(0编辑  收藏  举报