只说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. python3学习笔记(1)

    一.模块初识(接上篇)模块(库)分为两种:1.标准库:不需要安装直接可以导入的库,例:getpass.2.第三方库:必须要下载安装才可以使用.注:编辑的模块文件名不能与导入的模块名重复. 在模块文件中 ...

  2. git使用(上)-----基本的方法

    git应该是一项必须要掌握的工具.先简述它和SVN的区别 SVN是集中式版本控制系统,版本库是集中放在中央服务器的,而干活的时候,用的都是自己的电脑,所以首先要从中央服务器哪里得到最新的版本,然后干活 ...

  3. 实现基于Haproxy+Keepalived负载均衡高可用架构

    1.项目介绍: 上上期我们实现了keepalived主从高可用集群网站架构,随着公司业务的发展,公司负载均衡服务已经实现四层负载均衡,但业务的复杂程度提升,公司要求把mobile手机站点作为单独的服务 ...

  4. [Machine Learning]学习笔记-Logistic Regression

    [Machine Learning]学习笔记-Logistic Regression 模型-二分类任务 Logistic regression,亦称logtic regression,翻译为" ...

  5. 【C#入门教案-02】用记事本编写第一个C#程序-Hello World

    02-用记事本编写第一个C#程序-Hello World 广东职业技术学院  欧浩源 [1]进行.NET程序开发的最基本环境配备 .NET Framework + 代码编辑工具(记事本或Noetpad ...

  6. Linux 链接详解----动态链接库

    静态库的缺点: 库函数被包含在每一个运行的进程中,会造成主存的浪费. 目标文件的size过大 每次更新一个模块都需要重新编译,更新困难,使用不方便. 动态库: 是一个目标文件,包含代码和数据,它可以在 ...

  7. 开源纯C#工控网关+组态软件(七)数据采集与归档

    一.   引子 在当前自动化.信息化.智能化的时代背景下,数据的作用日渐凸显.而工业发展到如今,科技含量和自动化水平均显著提高,但对数据的采集.利用才开始起步. 对工业企业而言,数据采集日益受到重视, ...

  8. 为什么要初始化CSS?

    因为浏览器的兼容问题,不同浏览器对有些标签的默认值是不同的,如果没对CSS初始化往往会出现浏览器之间的页面显示差异. 当然,初始化样式会对SEO有一定的影响,但鱼和熊掌不可兼得,但力求影响最小的情况下 ...

  9. linux系统下安装配置解压版的MySQL数据库

    一.解压文件到当前目录 命令:tar -zxvf mysql....tar.gz 二.移动解压完成的文件夹到目标目录并更名mysql 命令:mv mysql-版本号 /usr/local/mysql ...

  10. 》》webpack打包成的文件

    /******/(function(modules) { // webpackBootstrap /******/ // The module cache /******/ var installed ...