C++内存模型实践探索

鼠扑  金牌会员 | 2024-10-18 22:38:17 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 922|帖子 922|积分 2766

前言

C++对象模型是个常见、且复杂的话题,本文基于Itanium C++ ABI通过程序实践介绍了几种 简单C++继承 场景下对象模型,尤其是存在虚函数的场景,并通过图的方式直观表达内存布局。
本文展示的程序构建情况为Ubuntu,glibc 2.24,gcc 6.3.0。由于clang和gcc编译器都是基于Itanium C++ ABI(详细信息参考gcc ABI policy),因此本文介绍的对象模型对clang编译的程序也基本实用。
虚函数表简介

虚函数表布局

含有虚函数的类,编译器会为其添加一个虚函数表(vptr)。
用如下程序验证含有虚函数的类的内存布局,该程序很简单,只界说了构造函数,虚析构函数,和一个int成员变量。
  1. // Derive.h
  2. class Base_C
  3. {
  4. public:
  5.     Base_C();
  6.     virtual ~Base_C();
  7. private:
  8.     int baseC;
  9. };
  10. // Derive.cc
  11. Base_C::Base_C()
  12. {
  13. }
  14. Base_C::~Base_C()
  15. {
  16. }
复制代码
gcc编译器可通过-fdump-class-hierarchy参数,查看类的内存布局。可得到如下信息:
  1. // g++ -O0 -std=c++11 -fdump-class-hierarchy Derive.h
  2. Vtable for Base_C
  3. Base_C::_ZTV6Base_C: 4u entries
  4. 0     (int (*)(...))0
  5. 8     (int (*)(...))(& _ZTI6Base_C)
  6. 16    (int (*)(...))Base_C::~Base_C
  7. 24    (int (*)(...))Base_C::~Base_C
  8. Class Base_C
  9.    size=16 align=8
  10.    base size=12 base align=8
  11. Base_C (0x0x7fb8e9185660) 0
  12.     vptr=((& Base_C::_ZTV6Base_C) + 16u)
复制代码
从类Base_C的界说来看,类占用的空间包括一个虚函数表指针vptr和一个整型变量。由于内存对齐的原因,类占用16字节
接下来看虚函数表,表中一共有4个entry,每个entry都是函数指针,指向详细的虚函数,因此每个entry在测试的呆板上编译占8字节(指针大小)。
留意看到表中虚析构函数有两个,这现实上是Itanium C++ ABI规定的:
  1. The entries for virtual destructors are <em><strong>actually pairs of entries</strong></em>.
  2. The first destructor, called the <strong>complete object destructor</strong>, performs the destruction without calling delete() on the object.
  3. The second destructor, called the <strong>deleting destructor</strong>, calls delete() after destroying the object.
  4. Both destroy any virtual bases; a separate, non-virtual function, called the base object destructor,
  5. 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文档中这两个条目均有详细的介绍:
  1. // typeinfo指针
  2. The <strong>typeinfo</strong> pointer points to the typeinfo object used for RTTI. <em>It is always present</em>.
  3. All entries in each of the virtual tables for a given class must point to the same typeinfo object.
  4. A correct implementation of typeinfo equality is to check pointer equality, except for pointers (directly or indirectly) to incomplete types.
  5. The typeinfo pointer is a valid pointer for polymorphic classes, i.e. those with virtual functions, and is zero for non-polymorphic classes.
复制代码
  1. // offset偏移
  2. 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>.
  3. 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.
  4. In a complete object virtual table, and therefore in all of its primary base virtual tables, the value of this offset will be zero.
  5. 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,
  6. 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

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

鼠扑

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表