下面是关于“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技术站