前言
C++对象模型是个常见、且复杂的话题,本文基于Itanium C++ ABI通过程序实践介绍了几种 简单C++继承 场景下对象模型,尤其是存在虚函数的场景,并通过图的方式直观表达内存布局。
本文展示的程序构建情况为Ubuntu,glibc 2.24,gcc 6.3.0。由于clang和gcc编译器都是基于Itanium C++ ABI(详细信息参考gcc ABI policy),因此本文介绍的对象模型对clang编译的程序也基本实用。
虚函数表简介
虚函数表布局
含有虚函数的类,编译器会为其添加一个虚函数表(vptr)。
用如下程序验证含有虚函数的类的内存布局,该程序很简单,只界说了构造函数,虚析构函数,和一个int成员变量。- // Derive.h
- class Base_C
- {
- public:
- Base_C();
- virtual ~Base_C();
- private:
- int baseC;
- };
- // Derive.cc
- Base_C::Base_C()
- {
- }
- Base_C::~Base_C()
- {
- }
复制代码 gcc编译器可通过-fdump-class-hierarchy参数,查看类的内存布局。可得到如下信息:- // g++ -O0 -std=c++11 -fdump-class-hierarchy Derive.h
- Vtable for Base_C
- Base_C::_ZTV6Base_C: 4u entries
- 0 (int (*)(...))0
- 8 (int (*)(...))(& _ZTI6Base_C)
- 16 (int (*)(...))Base_C::~Base_C
- 24 (int (*)(...))Base_C::~Base_C
- Class Base_C
- size=16 align=8
- base size=12 base align=8
- Base_C (0x0x7fb8e9185660) 0
- vptr=((& Base_C::_ZTV6Base_C) + 16u)
复制代码 从类Base_C的界说来看,类占用的空间包括一个虚函数表指针vptr和一个整型变量。由于内存对齐的原因,类占用16字节。
接下来看虚函数表,表中一共有4个entry,每个entry都是函数指针,指向详细的虚函数,因此每个entry在测试的呆板上编译占8字节(指针大小)。
留意看到表中虚析构函数有两个,这现实上是Itanium C++ ABI规定的:- The entries for virtual destructors are <em><strong>actually pairs of entries</strong></em>.
- The first destructor, called the <strong>complete object destructor</strong>, performs the destruction without calling delete() on the object.
- The second destructor, called the <strong>deleting destructor</strong>, calls delete() after destroying the object.
- Both destroy any virtual bases; a separate, non-virtual function, called the base object destructor,
- performs destruction of the object but not its virtual base subobjects, and does not call delete().
复制代码 虚析构函数在虚函数表中占用两条目,分别是complete object destructor和deleting destructor。
除了析构函数,虚函数表还有两个条目,紧靠析构函数的是typeinfo指针,指向范例信息对象(typeinfo object),用于运行时范例辨认(RTTI)。
第一个条目看起来可能比力生疏,是offset,该偏移存储了从当前虚表指针(vtable pointer)位置到对象顶部的位移。在ABI文档中这两个条目均有详细的介绍:- // typeinfo指针
- The <strong>typeinfo</strong> pointer points to the typeinfo object used for RTTI. <em>It is always present</em>.
- All entries in each of the virtual tables for a given class must point to the same typeinfo object.
- A correct implementation of typeinfo equality is to check pointer equality, except for pointers (directly or indirectly) to incomplete types.
- The typeinfo pointer is a valid pointer for polymorphic classes, i.e. those with virtual functions, and is zero for non-polymorphic classes.
复制代码- // offset偏移
- The <strong>offset</strong> to top holds the displacement to the top of the object from the location within the object of the virtual table pointer that addresses this virtual table, as a ptrdiff_t. <em>It is always present</em>.
- The offset provides a way to find the top of the object from any base subobject with a virtual table pointer. This is necessary for dynamic_cast<void*> in particular.
- In a complete object virtual table, and therefore in all of its primary base virtual tables, the value of this offset will be zero.
- For the secondary virtual tables of other non-virtual bases, and of many virtual bases, it will be negative. Only in some construction virtual tables will some virtual base virtual tables have positive offsets,
- due to a different ordering of the virtual bases in the full object than in the subobject's standalone layout.
复制代码 别的需要留意的是:vptr=((& Base_C::_ZTV6Base_C) + 16u),虽然虚函数表中有四个条目,但是vptr的指针现实上并不是指向表的起始位置,而是指向第一个虚函数的位置。
Base_C的内存布局如下图所示:
继承下的C++对象模型
单继承下C++对象模型
首先,看一个单继承场景的例子:
[code]// 此处省略类的实现部分class Base_C{public: Base_C(); virtual ~Base_C();private: int baseC;};class Base_D : public Base_C{public: Base_D(int i); virtual ~Base_D(); virtual void add(void) { cout |