c++对象模型

整理:侯捷老师的《面向对象高级编程》(下)

理解对象模型,才能真正理解多态和动态绑定.

1 成员函数和成员变量在内存中的分布

下面程序在内存中的布局如下所示:

class A {
public:
    virtual void vfunc1();
    virtual void vfunc2();
    void func1();
    void func2();
private:
    int m_data1;
    int m_data2;
};

class B : public A {
public:
    virtual void vfunc1();
    void vfunc2();
private:
    int m_data3;
};

class C : public B {
public:
    virtual void vfunc1();
    void vfunc2();
private:
    int m_data1;
    int m_data4;
};

代码很简单,继承也很清晰。

其在内存中的布局如下图所示:【这个图是精髓

 下面来解释一下这个图:

1 A内存结构:下图中用红框框起来。

 

对于A而言,其内存有两个数据成员:m_data1、m_data2,这是占类的大小的。

还有两个虚函数:virtual void vfunc1()和virtual void vfunc2()。那么虚函数在类中的内存分布是怎

样的呢?

首先,对于每个含有虚函数的类,系统都会自动生成虚函数表(简称虚表vtbl),虚表中存储A类中的每个虚函数的函数指针;然后,在A类实例化对象时,对象地址的前4个(32位机器)字节存储指向虚表的指针,简称虚指针(vptr)。

所以,对于A类而言,初始化对象时,内存中有两个数据成员和一个虚指针(虚指针指向虚表,虚表存储的是两个虚函数的地址)。

2 B的内存结构:下图用红框框起来

 B类继承了A类,那么它就有A类的数据成员和成员函数,所以它的数据域有从A类继承的m_data1

和m_data2,还有自己的数据m_data3;它又实现A类中的虚函数vfunc1,自己还有一个func2函

数,这个函数不是虚函数,是非静态函数,不占内存。

所以,对于B类而言,初始化对象时,内存中有三个数据成员(两个父类的,一个自己的),一个

虚指针(虚指针指向虚表,虚表中存储的是自己重写的vfunc1函数的地址和继承父类A的虚函数

vfunc2的地址)。

3 C的内存结构:下图用红框框起来

C类的分析和B类的分析一样,只是还要多考虑A类

C类继承了B类,那么它就有B中的数据成员和成员函数,所以它的数据域有m_data1、m_data2(这两个是从A类继承来的)、m_data3(从b类继承来的)、m_data4(自己的);它又实现了B类的虚函数vfunc1,还有一个自己的不占内存的非静态函数func2。

所以,对于C类而言,初始化对象时,内存中有四个数据成员(A类的两个、B类的一个、自己的一个),一个虚指针(虚指针指向虚表,虚表中存储C类重写的vfunc1函数的地址和继承父类的虚函数vfunc2函数的地址)。

先看成员变量部分: 对于成员变量来说,每个子类对象重都包含父类的成分,值得注意的是, C 类的m_data1 字段和父类 A 类的字段 m_data1 相同,这两个字段共存于 C 类的对象中.

再看函数的部分,每个含有虚函数的对象都包含一个特殊的指针 vptr ,指向存储函数指针的虚表 vtbl .编译器根据 vtbl 表中存储的函数指针找到虚函数的具体实现.这种编译函数的方式被称为动态绑定

更为一般的多态的过程:

(1)编译器在发现基类中有虚函数时,会自动为每个含有虚函数的类生成一份虚表,该表是一个一维数组,虚表里保存了虚函数的入口地址;

(2)编译器会在每个对象的前四个字节中保存一个虚表指针,即vptr,指向对象所属类的虚表。在构造时,根据对象的类型去初始化虚指针vptr,从而让vptr指向正确的虚表,从而在调用虚函数时,能找到正确的函数;

(3)所谓的合适时机,在派生类定义对象时,程序运行会自动调用构造函数,在构造函数中创建虚表并对虚表初始化。在构造子类对象时,会先调用父类的构造函数,此时,编译器只“看到了”父类,并为父类对象初始化虚表指针,令它指向父类的虚表;当调用子类的构造函数时,为子类对象初始化虚表指针,令它指向子类的虚表;

(4)当派生类对基类的虚函数没有重写时,派生类的虚表指针指向的是基类的虚表;当派生类对基类的虚函数重写时,派生类的虚表指针指向的是自身的虚表;当派生类中有自己的虚函数时,在自己的虚表中将此虚函数地址添加在后面;

这样指向派生类的基类指针在运行时,就可以根据派生类对虚函数重写情况动态的进行调用,从而实现多态性。

2 静态绑定和动态绑定

  • 对于一般的非虚成员函数来说,其在内存中的地址是固定的,编译时只需将函数调用编译成 call 命令即可,这被称为静态绑定.
  • 对于虚成员函数,调用时根据虚表 vtbl 判断具体调用的实现函数,相当于先把函数调用翻译成 (*(p->vptr)[n])(p) ,这被称为动态绑定.
     

静态绑定和动态绑定编译出的汇编代码如下所示:

 

虚函数触发动态绑定的条件是同时满足以下3个条件:
1. 必须是通过指针来调用函数.(实测,通过 . 运算符调用不会触发动态绑定)
2. 指针类型是对象的本身父类.
3. 调用的是虚函数.

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值