曲愍糙 发表于 6 天前

C++对象模型实践探索

前言

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++对象模型

首先,看一个单继承场景的例子:
// 此处省略类的实现部分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
页: [1]
查看完整版本: C++对象模型实践探索