C++ 类中有虚函数(虚函数表)时 内存分布详解

下面是关于“C++ 类中有虚函数(虚函数表)时 内存分布详解”的完整攻略:

1. 什么是虚函数

在 C++ 中,虚函数是指在基类中使用 virtual 关键字声明的成员函数。虚函数的特点是,在继承关系中,它能够被子类重写并被动态绑定。

2. 虚函数表

为了实现虚函数的动态绑定,编译器会在包含虚函数的类中生成一个虚函数表(Virtual Table,VTABLE)。VTABLE 是一个由函数指针组成的数组,每个指针指向对应成员函数的实现。

当程序运行时,每个对象都会在其内存布局中拥有一个指向 VTABLE 的指针。这个指针指向的 VTABLE 的内容是由该对象所属类的共享函数实现的指针构成的。这些共享函数实现也包括由该类的所有虚函数实现组成的数组。

每个虚函数在 VTABLE 中的索引位置就是编译器根据函数声明顺序决定的。如果一个类中有 M 个虚函数,那么 VTABLE 的前 M 个指针就指向对应虚函数的实现。当调用对象的虚函数时,程序根据对象的 VTABLE 指针找到对应的虚函数实现。

3. 类中有虚函数时的内存分布

假设有这样一个简单的类:

class Base {
public:
    virtual void func1() {}
    virtual void func2() {}
    int var1;
    int var2;
};

那么这个类在内存中的布局是这样的:

+-------------+
| VTABLE PTR  | --> +--------------+
+-------------+     | Base::func1() |
| var1        |     +--------------+
+-------------+     | Base::func2() |
| var2        |     +--------------+
+-------------+

当我们创建一个 Base 类的对象时,该对象在内存中的分布如下:

+-------------+
| VTABLE PTR  | --> +--------------+
+-------------+     | Base::func1() |
| var1        |     +--------------+
+-------------+     | Base::func2() |
| var2        |     +--------------+
| Padding     |     | Padding      |
+-------------+     +--------------+

其中,Padding 表示内存对齐的填充字节。

具有虚函数的类的内存分布中,第一个成员变量是一个指向 VTABLE 的指针。

4. 示例说明

下面我们用两个示例来说明类中带有虚函数的内存分布:

示例一

class Base {
public:
    Base(int a = 0) : m_a(a) {}
    virtual ~Base() {}
    virtual void func() { cout << "Base: " << m_a << endl; }
protected:
    int m_a;
};

class Derived : public Base {
public:
    Derived(int a = 0, int b = 0) : Base(a), m_b(b) {}
    virtual ~Derived() {}
    virtual void func() { cout << "Derived: " << m_b << endl; }
private:
    int m_b;
};

int main()
{
    Base* p = new Derived(1, 2);
    delete p;
    return 0;
}

这个程序中,我们创建了一个 Base 类型的指针指向了一个 Derived 类型的对象。在对象被销毁时,会发生什么事情呢?

在程序执行时,会分别为 Base 和 Derived 类生成两个 VTABLE。Base 类的 VTABLE 中有一个条目指向 Base::func(),Derived 类的 VTABLE 中也有一个条目指向 Derived::func()。因为 Derived 类继承自 Base 类,所以 Derived 类的 VTABLE 中还要包含 Base 类的 VTABLE。

我们来看一下这个过程中,内存是如何被分配和释放的。首先,创建 Derived 类对象时,对象在内存中的布局如下:

+------------+
| VTABLE PTR | ----------------> Base::VTABLE
+------------+
| Base::m_a  |
+------------+
| Derived::m_b|
+------------+

此时,VTABLE PTR 中存储的地址是 Base::VTABLE 的地址。

接下来,当向对象发送虚函数消息时,程序会根据对象的 VTABLE PTR 指向的 VTABLE 中的索引位置找到对应的虚函数实现。在上述程序中,当执行 p->func() 时,程序会调用 Derived::func()。

在对象被销毁时,程序会实现作为其基类的对象的析构函数。

当程序调用 delete p 时,首先会给对象的析构函数传递 Base 类的 VTABLE。因此,Derived 类的析构函数会被调用。接着,Derived 类的析构函数会调用基类 Base 的析构函数。在这个过程中,内存释放顺序与对象创建时的内存分配顺序相反。即先释放 Derived 类成员变量,再释放 Base 类成员变量。

在本例中,当对象被销毁时,程序相当于执行了以下语句:

Derived::~Derived();
Base::~Base();

由于 Derived 类的析构函数是虚函数,所以会调用 Derived 类的析构函数。在 Derived 类析构函数执行完后,会依次调用 Base 类的析构函数。此时 Base 类的对象已经被完全释放,整个程序的内存空间也被释放。

示例二

下面我们来看一个类中包含多个虚函数的情况。

class Base {
public:
    virtual ~Base() {}
    virtual void func1() {}
    virtual void func2() {}
protected:
    int m_a;
};

int main()
{
    Base obj;
    Base* p = new Base;
    p->func1();
    p->func2();
    delete p;
    return 0;
}

在这个程序中,我们定义了一个 Base 类型的对象和一个 Base 类型的指针。在指针所指向的对象上调用了一些虚函数,并最终删除了该对象。

类中的虚函数表在程序运行时被创建,并从对象的内存结构中分离出来。在程序运行时,每个对象都会有一个指针指向对应的虚函数表。

在本例中,Base 类中有两个虚函数 func1() 和 func2(),因此 Base 类的虚函数表中有两个条目,分别指向这两个虚函数。在 Base 类的对象中,第一项指向虚函数表的指针和第二项成员变量的地址重合。

下面是 Base 类对象在内存中的分布:

+---------+     +-------------+
| VT PTR  | ----> | Base::func1()|
+---------+     +--------------+
| var1    |     | Base::func2()|
----------+     +--------------+

执行这个程序时,对象的内存布局和 VTABLE 的创建只会发生一次。在对象销毁时,其内存空间会被完全释放。

以上是关于“C++ 类中有虚函数(虚函数表)时 内存分布详解”的完整攻略,希望能对您有所帮助。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:C++ 类中有虚函数(虚函数表)时 内存分布详解 - Python技术站

(0)
上一篇 2023年6月27日
下一篇 2023年6月27日

相关文章

  • WinXP系统关机时提示“dwwin.exe初始化失败”的故障分析及四种解决方法

    WinXP系统关机时提示“dwwin.exe初始化失败”的故障分析及四种解决方法 问题描述: 在使用WinXP系统时,可能会出现关机时提示“dwwin.exe初始化失败”的情况,这个问题会导致系统不能正常关机,严重影响用户体验。 故障分析: 症状描述 出现“dwwin.exe初始化失败”的提示信息时,可能会伴随着蓝屏、死机等问题。 故障原因 “dwwin.e…

    other 2023年6月20日
    00
  • 爬虫简介、requests基础用法、urlretrieve()

    爬虫简介、requests基础用法、urlretrieve() 爬虫简介 爬虫(英文名:web crawler 或 spider),是一种自动获取网页内容的程序。网页内容包括:文本、图片、音频、视频等。爬虫工作的模式一般是模拟浏览器行为,向目标网站发送 HTTP 请求,获取响应数据,然后解析数据提取需要的信息。爬虫常用于搜索引擎抓取网页、数据分析、数据挖掘等…

    其他 2023年3月28日
    00
  • Java批量修改文件名的实例代码

    下面是关于Java批量修改文件名的完整攻略: 1. 确定需求与实现思路 在开始编写代码之前,我们需要明确自己的需求以及代码实现的思路。这一步很重要,这样可以避免在编写代码时迷失方向,还可以削减后期的修改时间。在本例中,我们需要批量修改指定文件目录下的所有文件名,将文件名的后缀改为小写,保留文件名不变。我们可以按照以下步骤来实现: 获取指定目录下所有文件的文件…

    other 2023年6月26日
    00
  • Django使用echarts进行可视化展示的实践

    ata.values, type: ‘bar’ }] }; chart.setOption(option); } 在这个示例中,我们使用Ajax请求从`/chart_data`获取数据,并使用Echarts将数据渲染为柱状图。 ### 步骤四:配置Django路由 最后,我们需要配置Django的URL路由,将请求映射到相应的视图。以下是一个简单的示例: `…

    other 2023年8月15日
    00
  • knockoutjs快速入门(经典)

    KnockoutJS快速入门(经典) KnockoutJS是一款流行的JavaScript框架,用于构建动态的Web应用程序。它采用MVVM(Model-View-ViewModel)模式,可以将数据模型和视图分离,使得开发员可以更加专注于业务逻辑的实现。本文将介绍KnockoutJS的快速入门,包括如何创建ViewModel、如何绑定数据和如何处理用户交互…

    other 2023年5月9日
    00
  • 详解js中let与var声明变量的区别

    详解js中let与var声明变量的区别 在JavaScript中,我们可以使用let和var关键字来声明变量。尽管它们都可以用于声明变量,但它们在作用域和变量提升方面有一些重要的区别。 作用域 var声明的变量具有函数作用域,而let声明的变量具有块级作用域。 函数作用域意味着var声明的变量在整个函数内部都是可见的,而块级作用域意味着let声明的变量只在声…

    other 2023年8月20日
    00
  • 代理服务器CCProxy安装与图文设置方法

    下面是“代理服务器CCProxy安装与图文设置方法”的详细攻略。 安装 首先,你需要下载CCProxy的安装文件,可以从官网(http://www.youngzsoft.net/ccproxy/)下载。下载完成后,双击安装文件,按照提示进行安装,安装完成后,启动CCProxy。 配置 CCProxy 配置代理服务器 打开CCProxy,单击“选项”按钮,选择…

    other 2023年6月27日
    00
  • 聊聊java中引用数据类型有哪些

    聊聊Java中引用数据类型有哪些 Java中有两种数据类型:基本数据类型和引用数据类型。基本数据类型直接存储数据本身的值,而引用数据类型存储的是对象的引用,即对象在内存中的地址。 Java中的引用数据类型包括: 对象(Object): Java中最基本的引用数据类型,除了基本数据类型外,Java中的所有数据类型都是以对象的形式出现。 Object obj =…

    other 2023年6月27日
    00
合作推广
合作推广
分享本页
返回顶部