只说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. React+Redux实现追书神器网页版

    引言 由于现在做的react-native项目没有使用到redux等框架,写了一段时间想深入学习react,有个想法想做个demo练手下,那时候其实还没想好要做哪一个类型的,也看了些动漫的,小说阅读, ...

  2. LSA和pLSA的比较

    Comparison   LSA pLSA 1. Theoretical background Linear Algebra Probabilities and Statistics 2. Objec ...

  3. Xshell显示图形化界面

    前言 很久没用过图形化界面了,都忘记怎么使用了.... 依据以往的经验都是由环境变量DISPLAY设置,然后就能连接了,每天也是匆匆忙忙的就过了一天,都不知道干了啥,分配的时间也少,但是一直纠结,进行 ...

  4. 在ThinkPHP中使用常量解决路由常规地址不安全传送数据问题

    在ThinkPHP搭建项目的同时,会考虑到后期对静态页面的维护问题, 在项目的不断完善的同时,会有大量图片,css文件,以及js文件等一些容易修改.添加.或者删除的资源 如果在中后期对各个静态页面,j ...

  5. php常用面试题

    1. 有一列数的规则如下 1.1.2.3.5.8.13.21.34... 求第30位数是多少.写出相关函数和算法名称 //$pxx = array(1,1);//for($i=2;$i<=29; ...

  6. 整理下git常用命令

    Git工作示意图 一.新建代码库 ::在当前目录新建一个Git代码库git init::新建一个目录,将其初始化为Git代码库git init [project-name]::下载一个项目和它的整个代 ...

  7. Natural Hazards 隐私政策

    隐私政策 本应用尊重并保护所有使用服务用户的个人隐私权.为了给您提供更准确.更有个性化的服务,本应用会按照本隐私权政策的规定使用和披露您的个人信息.但本应用将以高度的勤勉.审慎义务对待这些信息.除本隐 ...

  8. 一道看似dp实则暴力的题 Zombie's Treasure Chest

     Zombie's Treasure Chest 本题题意:有一个给定容量的大箱子,此箱子只能装蓝宝石和绿宝石,假设蓝绿宝石的数量无限,给定蓝绿宝石的大小和价值,要求是获得最大的价值 题解:本题看似是 ...

  9. 《Linux调优工具oprofile的演示分析》

    根据CPU架构oprofile采样的触发有两种模式:1) NMI模式: 利用处理器的performance counter功能, 指定counter的类型type和累进数量count. 比如 type ...

  10. iOS日历中给一个事件加入多个提醒

    大熊猫猪·侯佩原创或翻译作品.欢迎转载,转载请注明出处. 假设认为写的不好请多提意见,假设认为不错请多多支持点赞.谢谢! hopy ;) iOS自带的日历应用中,我们最多仅仅能给一个事件设置2个提醒, ...