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日

相关文章

  • telnetipport

    以下是关于“telnet ip port”的完整攻略,包括定义、使用方法、示例说明和注意事项。 定义 Telnet是一种用于远程登录到计算机的协议,可以通过命令行界面(CLI)与远程计算机进行交互。telnet ip port是一种使用Telnet协议连接到远程计算机的命令,其中ip是远程计算机的地址,port是要连接的端口号。 使用方法 以下是使用teln…

    other 2023年5月8日
    00
  • Win10中怎么利用的一个位置管理所有存储空间?

    在Windows 10中,你可以使用“存储空间”功能来管理所有的存储设备和磁盘空间。下面是一个详细的攻略,包含了两个示例说明: 步骤1:打开“存储空间”设置 首先,点击任务栏上的Windows图标,然后在弹出的菜单中选择“设置”图标(齿轮状图标)。接下来,在“设置”窗口中,点击“系统”选项。 在“系统”选项卡中,你会看到一个侧边栏,选择“存储”选项。 步骤2…

    other 2023年8月1日
    00
  • 微信小程序 自定义创建详细介绍

    下面是详细讲解“微信小程序自定义创建详细介绍”的完整攻略。 一、创建小程序 进入微信公众平台,登录并进入开发者工具页面。 点击左侧导航栏中的“开发”按钮,在页面右侧的菜单栏中选择“开发设置”。 在“开发设置”页面中,点击“添加开发者”按钮,填写相关信息,并勾选“我已知晓该操作不可逆”,最后点击“提交”按钮。 返回“开发者工具”页面,点击左侧导航栏中的“小程序…

    other 2023年6月25日
    00
  • 三星note4怎么刷机 三星galaxy note4刷机图文教程

    三星Note4刷机攻略 准备工作 在开始刷机之前,请确保你已经完成以下准备工作: 备份数据:刷机过程中可能会导致数据丢失,所以务必提前备份重要的数据,如联系人、短信、照片等。 充电:确保你的三星Note4电量充足,以免在刷机过程中因电量不足导致意外中断。 下载所需文件:下载刷机所需的文件,包括刷机工具和刷机包。你可以在三星官方网站或相关论坛上找到适用于你的N…

    other 2023年8月5日
    00
  • 关于sql:oracle优化器提示xmlagg函数

    以下是关于“关于SQL:Oracle优化器提示XMLAGG函数”的完整攻略,包含两个示例。 SQL:Oracle优化器提示XMLAGG函数 在Oracle中,XMLAGG函数用将多个行合并为一个XML文档。当我们在查询中使用XMLAGG函数时,Oracle化器可能提示我们使用XMLTABLE函数来提高查询性能。以下是关于如何使用XMLAGG函数和XMLTAB…

    other 2023年5月9日
    00
  • 如何在yml配置文件中使用中文注解

    如果需要在yml配置文件中添加中文注解,可以按照以下步骤进行: 确保yml文件开头的标记为—。在这个标记下方添加注解即可。 在需要注解的行前面添加’#’符号,然后在’#’后面添加中文注解。 例如,下面是一个基本的yml配置文件,我们需要对其中的一些参数进行注解,以便其他人易于理解: — name: my-app server: port: 8080 …

    other 2023年6月25日
    00
  • uniapp实现全局变量的几种方式总结

    UniApp实现全局变量的几种方式总结 在UniApp中,实现全局变量可以帮助我们在不同页面之间共享数据。下面是几种实现全局变量的方式的详细讲解。 1. 使用Vuex Vuex是Vue.js的状态管理库,可以在UniApp中使用它来实现全局变量。以下是使用Vuex的步骤: 安装Vuex:在项目根目录下执行以下命令安装Vuex。 npm install vue…

    other 2023年7月29日
    00
  • spreadsheetgear插件屏蔽鼠标右键的方法

    介绍 SpreadsheetGear是一个.Net平台下的电子表格组件库,可以在.Net框架下为Windows、Web和移动设备等各种平台提供电子表格计算功能。在其中,有一种操作,可以防止用户使用鼠标右键在工作表上进行操作。 屏蔽鼠标右键的方法 在SpreadsheetGear中,可以通过下面的操作屏蔽鼠标右键的方法: 绑定事件 在应用程序初始化时,需要添加…

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