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

         程序运行结果:

    (完)

  • 相关阅读:
    Java Nashorn--Part 4
    Java Nashorn--Part 3
    Java Nashorn--Part 2
    Java Nashorn--Part 1
    Java 异步 IO
    代码天天写,快乐天天有!
    比迷路更可怕的,是对读书的迷失。
    《寄生兽》观后感
    浅谈生活
    8月份的尾巴
  • 原文地址:https://www.cnblogs.com/helloamigo/p/3592923.html
Copyright © 2020-2023  润新知