zoukankan      html  css  js  c++  java
  • 《转载》虚函数在对象中的内存布局

    这篇文章很不错!!

    http://patmusing.blog.163.com/blog/static/13583496020103255219855/

    典型地,C++通过虚函数实现多态性。多态性的定义:“无论发送消息的对象属于什么类,他们均发送具有相同形式的消息,对消息的处理方式可能随接受消息的对象而变。”具体地说,“在某个基类上建立起来的类的层次结构中,可以对任何一个派生类的对象中的同名成员函数进行调用,而被调用的成员变量所提供的处理可以随其所属的类而改变。”虚函数首先是一种成员函数,它可以在该类的派生类中被重新定义并被赋予另外一种处理功能。

    我们先来看看下面的例子:

    #include <iostream>

    using namespace std;

     

    class A

    {

    private:

             int a;

    public:

             void funA0()

             {

                       cout << "This is funA0 in class A" << endl;

             }

     

             void setA(int a)

             {

                       this->a = a;

             }

    };

     

    class B

    {

    private:

             int b;

    public:

             virtual void vfunB0()

             {

                       cout << "This is vfunB0 in class B" << endl;

             }

     

             void setB(int b)

             {

                       this->b = b;

             }

    };

     

    class C

    {

    private:

             int c;

    public:

             virtual void vfunC0()

             {

                       cout << "It's in vfunC0 in class C" << endl;

             }

     

             virtual void vfunC1()

             {

                      cout << "It's in vfunC1 in class C" << endl;

             }

     

             void setC(int c)

             {

                       this->c = c;

             }

    };

     

    class D : public B

    {

    private:

             int d0;

             int d1;

    public:

             void setD0(int d0)

             {

                       this->d0 = d0;

             }

     

             void setD1(int d1)

             {

                       this->d1 = d1;

             }

    };

     

    int main(void)

    {

             A aa;

             B bb;

             C cc;

             D dd;

     

             cout << "Size of class A = ";

             cout << sizeof(aa) << endl;

     

             cout << "Size of class B = ";

             cout << sizeof(bb) << endl;

           

             cout << "Size of class C = ";

             cout << sizeof(cc) << endl;

     

             cout << "Size of class D = ";

             cout << sizeof(dd) << endl;

     

             dd.setD0(10);

             dd.setD1(11);

     

             cout << *((unsigned long*)(&dd) + 1) << endl;

             cout << *((unsigned long*)(&dd) + 2) << endl;

             cout << *((unsigned long*)(&dd) + 3) << endl;

     

             return 0;

    }

    输出结果:

    图1

    class A中定义了一个整形的成员变量a,在32-bit的操作系统中,整形是4Bytes,因此aa的大小是4Bytes;

    图2

    class B中定义了一个整形的成员变量b和一个虚函数vfunB0,其中整形成员变量b需要4Bytes,vfun0将导致class B生成一个指向virtual table的指针vfptr (其中virtual table中拥有该类所有虚函数的指针)。在32-bit的操作系统中一个指针需要32 bits或者4Bytes来表示,因此输出的bb的大小是8Bytes;

     

    图3

    class C中定义了一个整形的成员变量c和两个虚函数vfunC0、vfunC1,整形成员变量c需要4Bytes,两个虚函数的声明导致class C生成一个指向virtual table的指针vfptr (其中virtual table中拥有该类所有虚函数的指针)。在32-bit的操作系统中一个指针需要32 bits或者4Bytes来表示,因此输出的cc的大小也是是8Bytes。

    图4

    由此可见,只要在一个类中声明了一个虚函数或者多个虚函数,该类在实例化的时候,总是隐式地生成一个且仅一个指向virtual table的指针vfptr,一个或者多个该类虚函数的指针将被存放在virtual table中。另外,如果一个类存在虚函数,那么vfptr总是在最前面的,即在所有成员变量之前,如上图所示。

    class D中定义了一个整形的成员变量d0、d1,整形成员变量d0、d1各需要4Bytes,另外由于它继承了class B,由于在class B中也定义了一个整形成员变量b (尽管它是private的),这个也需要4Bytes,同时在class B 也定义的一个虚函数,也被class D继承了,而“虚函数不管被继承多少次,仍然是虚函数”,因此class D也需要在被实例化的时候隐式生成一个指向virtual table的指针vfptr。由此得到dd的大小是16Bytes。

    图5

    为什么说是这样的顺序呢?即b、d0、d1,而不是d0、d1、b呢,从下面的代码所产生的结果就可以很清楚地知道这一点。

             dd.setD0(10);             // 设置d0 = 10

             dd.setD1(11);             // 设置d1 = 11

     

             // *((unsigned long*)(&dd) + 0) 就是Virtual Table的指针

             //

             cout << *((unsigned long*)(&dd) + 1) << endl;                    // 参照前面console的输出:3435973836

             cout << *((unsigned long*)(&dd) + 2) << endl;                    // 参照前面console的输出:10

             cout << *((unsigned long*)(&dd) + 3) << endl;                    // 参照前面console的输出:11

    因此可以判定最后两个分别是d0、d1,紧随vfptr之后的是class B中的似有成员变量b,由于没有被赋值,所以输出的结果是任给的。

    由此我们可以得到一个重要的结论尽管是私有成员变量,诸如class D中的d0d1,我们还是可以通过类似

    *((unsigned long*)(&dd) + offset)的方式轻松地访问到,甚至其基类的私有成员变量,诸如class B中的b。(这样似乎不太安全,但事实上就是这样的,当然很少有人这么做)

    同一个类的所有对象共享同一个成员函数的地址空间(虚函数除外),而每个对象有独立的成员变量地址空间。可以说成员函数是类拥有的,成员变量是类的对象拥有的。

    虚函数和虚继承问题相当复杂,尤其是两者同时存在的时候。但并不是很难,也非常有意思。
  • 相关阅读:
    Java连载63-异常处理try...catch...、方法getMessageyu printStackTrace
    Python连载58-http协议简介
    Java连载62-使用throws关键字处理异常
    HTML连载57-相对定位和绝对定位
    Java连载61-异常的机制与分类
    Python连载57- 邮件头和主题、解析邮件
    Java连载60-类之间的六种关系
    [Java] 数据库编程JDBC
    [bug] MySQL-Front连接MySQL 8.0失败
    [bug]mysql: The server time zone value '&#214;&#208;&#185;&#250;&#177;&#234;&#215;&#188;&#202;&#177;&#188;&#228;' is unrecognized or represents more than one time zone
  • 原文地址:https://www.cnblogs.com/xiangshancuizhu/p/1987446.html
Copyright © 2011-2022 走看看