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

yizhihongxing

下面是关于“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日

相关文章

  • dataframeunique函数

    当然,我很乐意为您提供有关“DataFrame.unique函数”的完整攻略。以下是详细的步骤和两个示例: 1 DataFrame.unique函数 DataFrame.unique函数是Pandas库中的一个函数,它用于返回DataFrame中唯一值数组。以下是使用DataFrame.unique函数的步骤: 1.1 导入Pandas库 首先,您需要在Py…

    other 2023年5月6日
    00
  • Java 基础语法之解析 Java 的包和继承

    Java 基础语法之解析 Java 的包和继承 Java 作为面向对象编程的语言,对于代码的组织和扩展提供了很好的支持。其中包和继承作为两个重要的概念,是 Java 中的核心特性之一。本文将从概念入手,详细讲解 Java 中的包和继承的原理和使用方法。 Java 包的概念和使用 Java 中的包可以看作是一种类的组织方式,类似于文件夹的概念。通常情况下,我们…

    other 2023年6月27日
    00
  • Android实现自定义的卫星式菜单(弧形菜单)详解

    Android实现自定义的卫星式菜单(弧形菜单)详解 在Android应用中实现自定义的卫星式菜单(也称为弧形菜单)可以为用户提供一种独特的交互体验。本攻略将详细介绍如何实现这样的菜单,并提供两个示例说明。 步骤一:准备工作 在开始之前,确保你已经具备以下准备工作: Android开发环境的搭建和配置。 一个新的Android项目。 步骤二:创建自定义Vie…

    other 2023年8月21日
    00
  • Android中使用开源框架eventbus3.0实现fragment之间的通信交互

    Android中使用开源框架EventBus 3.0实现Fragment之间的通信交互攻略 简介 在Android开发中,Fragment之间的通信交互是一个常见的需求。EventBus是一个优秀的开源框架,可以简化Fragment之间的通信过程。本攻略将详细介绍如何在Android中使用EventBus 3.0实现Fragment之间的通信交互。 步骤 步…

    other 2023年9月7日
    00
  • 详解React项目的服务端渲染改造(koa2+webpack3.11)

    详解React项目的服务端渲染改造(koa2+webpack3.11) 1. 概述 本文将介绍如何将一个React项目改造成服务端渲染的形式,并使用Koa2和webpack3.11完成。 服务端渲染的好处是能够提高网站的SEO和首屏渲染速度,并且能够更好地应对一些搜索引擎不友好的单页面应用(SPA)。通过本文,你将掌握如何在一个React项目中加入服务端渲染…

    other 2023年6月27日
    00
  • ps怎么批量制作带身份证和学生姓名的学生信息卡?

    当你需要批量制作带有身份证和学生姓名的学生信息卡时,你可以使用以下步骤: 准备数据:首先,你需要准备一个包含学生身份证号码和姓名的数据表格。可以使用Excel或其他电子表格软件创建一个表格,其中包含两列,一列是身份证号码,另一列是学生姓名。确保数据表格中的每一行都对应一个学生的信息。 创建模板:接下来,你需要创建一个信息卡的模板。你可以使用任何适合你的设计软…

    other 2023年8月16日
    00
  • Foobar2000播放器怎么从音乐文件名获取标签?

    首先,需要明确一下Foobar2000中的标签是指音乐文件的元数据,比如歌曲名称、歌手、专辑等信息。在很多情况下,我们的音乐文件的名称并不完整或准确,因此需要利用Foobar2000自动从文件名中获取标签。 以下是获取标签的步骤: 在Foobar2000中打开你要获取标签的音乐文件所在的播放列表。 选中需要获取标签的音乐文件。 右键单击选中的音乐文件,并选择…

    other 2023年6月26日
    00
  • 6招为智能abc输入法提速 输入大写金额再也不用愁啦

    6招为智能ABC输入法提速 输入大写金额再也不用愁啦 简介 智能ABC输入法是一款智能化的输入法工具,可以帮助用户快速输入大写金额。本攻略将介绍6个技巧,帮助您更高效地使用智能ABC输入法。 技巧一:使用快捷短语 智能ABC输入法支持设置快捷短语,可以将常用的大写金额词组设置为快捷短语,以便快速输入。例如,您可以将\”一百元\”设置为快捷短语\”100元\”…

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