IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> c++对象模型 -> 正文阅读

[C++知识库]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. 调用的是虚函数.

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-04-26 11:24:29  更:2022-04-26 11:25:56 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 22:08:17-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码