只说C++对象模型在内存中如何分配这是不现实的,所以这里选择VS 2013作为调试环境具体探讨object在内存中分配情况.目录给出了具体要探讨的所有模型,正文分标题依次讨论.水平有限,如有错误之处请多包涵如若能及时反馈于我请接受我的谢意.

目录

  1. 简单对象模型
  2. 单继承对象模型
  3. 多继承对象模型
  4. 菱形多继承对象模型
  5. 虚单继承对象模型
  6. 虚多继承对象模型
  7. 菱形虚多继承对象模型

简单对象模型

首先给出具体的模型和类的代码,然后我们会验证模型是否正确:) 

class base {
public:
base() :baseData(5) {}
virtual ~base() {}
int baseFunc() { std::cout << "base::baseFunc"; return 0; }
static int sBaseFunc() { std::cout << "base::sBaseFunc"; return 0; }
virtual int vfunc() { std::cout << "base::vfunc()"; return 0; }
private:
int baseData;
static int sBaseData;
};

这个简单的类完备包含了静态类成员函数,类成员函数,类数据成员,静态类数据成员,虚函数.我们可以注意到base类中所有成员函数(指非静态成员函数, 下文同),静态成员函数和静态数据成员都存在于对象内存之外,也就是定义一个对象不会有额外的开销来保存这些内容,这也符合我们的常识.所以这个简单的对象在内存中主要表现为存储非静态数据成员和虚函数.更具体而言一个对象会保存非静态数据成员和一个指向虚函数表的指针(vfptr,如果有虚函数的话),我们常说C++的编译器会偷偷做很多事情这里便是一个例子,这里的vfptr会被编译器在合适的地方安插进代码,这个合适的地方通常就是default
constructor(如果没有显式声明default constructor编译器会合成一个(当然这也得视情况而定),不过这个不再讨论范围内); 下面是详细的验证,

int main() {
base b;
//在vs的编译器实现中虚函数表指针放在对象抵首位,所以&b也就相当于取vfptr地址;
int * vfptr = (int *)(&b);
//给virtual int vfunc()别名一个函数指针简化代码
using vfuncType = int(*)();
//指向虚函数表第一项的指针
int *vtablePtr = (int*)(*vfptr);
//vtablePtr+1表示获取虚函数vfunc的地址,因为直接的vtablePtr是指向析构函数
vfuncType vfunc= vfuncType(*(vtablePtr + 1));
//调用虚函数,输出base::vfunc()
(*vfunc)(); //输出5
int *dataPtr = (int *)(&b) + 1;
std::cout << *dataPtr;
system("pause");
}

值得注意的是vtablePtr-1就是指向type_info,而type_info主要用以支持RTTI,与主题相差较远这里不做赘述.

单继承对象模型

只要明白了简单对象模型接来的单/多继承也就变得很简单了.

class derived : public base {
public:
derived() :derivedData(10) {}
virtual int vfunc() { std::cout << "derived::vfunc()"; return 0; }
virtual void newVF() { std::cout << "derived::newVF();"; }
private:
int derivedData;
};

可以看到这里x派生类中新增加的newVF虚函数被置于虚函数表最下方,然后如果派生类重写了虚函数就用派生类重写的版本替代基类的版本,其他顺序不变.同样给出验证:

int main() {
derived d;
int * vptf = (int *)(&d);
int * vfptr = (int *)(&d);
using vfuncType = int(*)();
using newVFType = void(*)();
int *vtablePtr = (int*)(*vfptr);
vfuncType vfunc = vfuncType(*(vtablePtr + 1));
newVFType nvfunc = newVFType(*(vtablePtr + 2));
(*vfunc)(); //derived::vfunc()
(*nvfunc)();//derived::newVF() int *baseDataPtr = (int *)(&d) + 1;
int *derivedDataPtr = (int *)(&d) + 2;
std::cout << *baseDataPtr<<*derivedDataPtr;
system("pause");
}

多继承对象模型

模型如下:

为了方便我们这里适当简化class

class base1 {
public:
base1() :base1Data(5) {}
virtual int vfunc() { std::cout << "base::vfunc()"; return 0; }
private:
int base1Data;
}; class base2 {
public:
base2() :base2Data(5) {}
virtual int vfunc() { std::cout << "base::vfunc()"; return 0; }
private:
int base2Data;
}; class derived : public base1, public base2 {
public:
derived() :derivedData(10) {}
virtual int vfunc() { std::cout << "derived::vfunc()"; return 0; }
virtual void newVF() { std::cout << "derived::newVF();"; }
private:
int derivedData;
};

图片基本上已经把我想说的说完了, 这里只需要注意一下

  • 派生类中新定义的虚函数会置于第一个虚函数表最下面而不是两个虚函数表都放置.
  • 基类数据成员会放置在指向该基类的虚函数表的指针下面,如果有必要之类还会进行内存alignment.

验证如下

int main() {
derived d;
//上帝原谅我这里用下划线
int *base1_vfptr = (int*)(&d);
int *base1_dataPtr = (int*)(&d) + 1;
int *base2_vfptr = (int*)(&d) + 2;
int *base2_dataPtr = (int*)(&d) + 3;
int *derived_dataPtr = (int*)(&d) + 4; int (*derived_vfunc)() = (int(*)())(*(int*)(*base1_vfptr));
void(*derived_newVF)() = (void(*)())(*((int*)(*base1_vfptr)+1));
int(*derived_vfunc1)() = (int(*)())(*(int*)(*base2_vfptr));
derived_vfunc();
derived_newVF();
derived_vfunc1();
std::cout << *base1_dataPtr << *base2_dataPtr << *derived_dataPtr;
system("pause");
}

菱形多继承对象模型

class root {
public:
root() :rootData(1) {}
virtual int vfunc() { std::cout << "root::vfunc()"; return 0; }
virtual void print() { std::cout << "root::print()"; }
private:
int rootData;
}; class base1 :public root {
public:
base1() :base1Data(2) {}
virtual int vfunc() { std::cout << "base::vfunc()"; return 0; }
private:
int base1Data;
}; class base2 :public root {
public:
base2() :base2Data(3) {}
virtual int vfunc() { std::cout << "base::vfunc()"; return 0; }
private:
int base2Data;
}; class derived : public base1, public base2 {
public:
derived() :derivedData(4) {}
virtual int vfunc() { std::cout << "derived::vfunc()"; return 0; }
virtual void newVF() { std::cout << "derived::newVF();"; }
private:
int derivedData;
};

这里我们root作为基类,然后base1 base2从root上派生出来,最后derived从base1 base2中派生出来实现一个菱形继承.我们看到上图第一个应该关注的是有两个rootData和root::print(),这对于追求效率的C艹是无法容忍的,所以后面引出虚继承解决这个问题(以便引出另一些问题:( ).至于为什么有两个稍微想一下就能明白,在单继承下派生类内存模型会储存基类的数据成员和虚函数,所以这里base1和base2分别储存了rootData和print(),最后derived的多重继承把每个相对于它而言的基类一块一块的放入内存,之所以说是一块一块是因为内存不会把vfptr放一块然后数据成员放一块而是像之前提及的分块处理.

未完待续.

探索C++对象模型的更多相关文章

  1. 读书笔记《深度探索c++对象模型》 概述

    <深度探索c++对象模型>这本书是我工作一段时间后想更深入了解C++的底层实现知识,如内存布局.模型.内存大小.继承.虚函数表等而阅读的:此外在很多面试或者工作中,对底层的知识的足够了解也 ...

  2. 柔性数组-读《深度探索C++对象模型》有感 (转载)

    最近在看<深度探索C++对象模型>,对于Struct的用法中,发现有一些地方值得我们借鉴的地方,特此和大家分享一下,此间内容包含了网上搜集的一些资料,同时感谢提供这些信息的作者. 原文如下 ...

  3. 柔性数组-读《深度探索C++对象模型》有感

    最近在看<深度探索C++对象模型>,对于Struct的用法中,发现有一些地方值得我们借鉴的地方,特此和大家分享一下,此间内容包含了网上搜集的一些资料,同时感谢提供这些信息的作者. 原文如下 ...

  4. [读书系列] 深度探索C++对象模型 初读

    2012年底-2014年初这段时间主要用C++做手游开发,时隔3年,重新拿起<深度探索C++对象模型>这本书,感觉生疏了很多,如果按前阵子的生疏度来说,现在不借助Visual Studio ...

  5. 拾遗与填坑《深度探索C++对象模型》3.3节

    <深度探索C++对象模型>是一本好书,该书作者也是<C++ Primer>的作者,一位绝对的C++大师.诚然该书中也有多多少少的错误一直为人所诟病,但这仍然不妨碍称其为一本好书 ...

  6. 拾遗与填坑《深度探索C++对象模型》3.2节

    <深度探索C++对象模型>是一本好书,该书作者也是<C++ Primer>的作者,一位绝对的C++大师.诚然该书中也有多多少少的错误一直为人所诟病,但这仍然不妨碍称其为一本好书 ...

  7. 深度探索C++对象模型

    深度探索C++对象模型 什么是C++对象模型: 语言中直接支持面向对象程序设计的部分. 对于各个支持的底层实现机制. 抽象性与实际性之间找出平衡点, 需要知识, 经验以及许多思考. 导读 这本书是C+ ...

  8. 《深度探索C++对象模型》读书笔记(一)

    前言 今年中下旬就要找工作了,我计划从现在就开始准备一些面试中会问到的基础知识,包括C++.操作系统.计算机网络.算法和数据结构等.C++就先从这本<深度探索C++对象模型>开始.不同于& ...

  9. C++的黑科技(深入探索C++对象模型)

    周二面了腾讯,之前只投了TST内推,貌似就是TST面试了 其中有一个问题,“如何产生一个不能被继承的类”,这道题我反反复复只想到,将父类的构造函数私有,让子类不能调用,最后归结出一个单例模式,但面试官 ...

  10. 深入探索C++对象模型(一)

    再读<深入探索C++对象模型>笔记. 关于对象 C++在加入封装后(只含有数据成员和普通成员函数)的布局成本增加了多少? 答案是并没有增加布局成本.就像C struct一样,memeber ...

随机推荐

  1. Office隐藏17年的漏洞CVE_2017_11882测试记录

    Office隐藏17年的漏洞CVE_2017_11882测试记录 创建时间: 2017/11/25 0:18 作者: CN_Simo 标签: Office漏洞 参考文章1:https://www.cn ...

  2. python3学习笔记(3)

    一.内置函数补充1.callable()检测传递的参数是否可以被调用.def f1() pass可以被调用f2 = 123不可以被调用2.chr()和ord()chr()将ascii码转换成字符,or ...

  3. 一个可扩展的深度学习框架的Python实现(仿keras接口)

    一个可扩展的深度学习框架的Python实现(仿keras接口) 动机 keras是一种非常优秀的深度学习框架,其具有较好的易用性,可扩展性.keras的接口设计非常优雅,使用起来非常方便.在这里,我将 ...

  4. listview相关代码整理

    虽然listview已经慢慢被替代了,  不过还是整理下 , 留作纪念吧 /** * 获取 listview 实际滚动的距离. [ 相对于listview的第一个项目左上角.] * * @return ...

  5. Thinkphp使用phpexcel导入文件并写入数据库

    现实中,我们往往很多地方都需要这样批量导入数据,除了phpexcel还有csv等方法可以解决 下面详细介绍一下使用方法 首先在官方下载安装包解压到本地,然后复制保存到tp框架下的vendor目录下 h ...

  6. 小程序采坑系列-this.setData

    今天踩了大坑,坑里还都是碎瓶渣子.. 先说一下基本使用.官网也有. 比如说你在main.js里面有这些变量.想修改某些值. data: { main_view_bgcolor: "" ...

  7. MFC中小笔记(四)

    12.编译透明化界面是出现  WS_EX_LAYERED  AC_SRC_ALPHA ULW_ALPHA ULW_OPAQUE  undeclared identifier ,搜索发现SDK版本过低. ...

  8. js计时函数实现秒表的开始-暂停-清零功能

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  9. 实践作业2:黑盒测试实践——小组任务分工 Day 1

    今日教学实验任务分配后,课下小组例会完成任务分工,具体分工如下: (1)系统需求分析--刘思佳 (2)设计测试用例--王俊杰 (3)编写.运行测试脚本--郜昌磊 (4)记录测试过程--吴慧杰 (5)记 ...

  10. 前端CSS的工程化——掌握Sass这四大特性就够了

    初遇 CSS,一见倾心 记得刚遇见css的时候,咱像是见了美人儿一样,简直是爱不释手啊,简简单单写几行算不上代码的代码,就能做出这么漂亮的东西,这也成了咱前端之路的最初动力. 然而,随着项目体量和页面 ...