探索C++对象模型
只说C++对象模型在内存中如何分配这是不现实的,所以这里选择VS 2013作为调试环境具体探讨object在内存中分配情况.目录给出了具体要探讨的所有模型,正文分标题依次讨论.水平有限,如有错误之处请多包涵如若能及时反馈于我请接受我的谢意.
目录
- 简单对象模型
- 单继承对象模型
- 多继承对象模型
- 菱形多继承对象模型
- 虚单继承对象模型
- 虚多继承对象模型
- 菱形虚多继承对象模型
简单对象模型
首先给出具体的模型和类的代码,然后我们会验证模型是否正确:)
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++对象模型的更多相关文章
- 读书笔记《深度探索c++对象模型》 概述
<深度探索c++对象模型>这本书是我工作一段时间后想更深入了解C++的底层实现知识,如内存布局.模型.内存大小.继承.虚函数表等而阅读的:此外在很多面试或者工作中,对底层的知识的足够了解也 ...
- 柔性数组-读《深度探索C++对象模型》有感 (转载)
最近在看<深度探索C++对象模型>,对于Struct的用法中,发现有一些地方值得我们借鉴的地方,特此和大家分享一下,此间内容包含了网上搜集的一些资料,同时感谢提供这些信息的作者. 原文如下 ...
- 柔性数组-读《深度探索C++对象模型》有感
最近在看<深度探索C++对象模型>,对于Struct的用法中,发现有一些地方值得我们借鉴的地方,特此和大家分享一下,此间内容包含了网上搜集的一些资料,同时感谢提供这些信息的作者. 原文如下 ...
- [读书系列] 深度探索C++对象模型 初读
2012年底-2014年初这段时间主要用C++做手游开发,时隔3年,重新拿起<深度探索C++对象模型>这本书,感觉生疏了很多,如果按前阵子的生疏度来说,现在不借助Visual Studio ...
- 拾遗与填坑《深度探索C++对象模型》3.3节
<深度探索C++对象模型>是一本好书,该书作者也是<C++ Primer>的作者,一位绝对的C++大师.诚然该书中也有多多少少的错误一直为人所诟病,但这仍然不妨碍称其为一本好书 ...
- 拾遗与填坑《深度探索C++对象模型》3.2节
<深度探索C++对象模型>是一本好书,该书作者也是<C++ Primer>的作者,一位绝对的C++大师.诚然该书中也有多多少少的错误一直为人所诟病,但这仍然不妨碍称其为一本好书 ...
- 深度探索C++对象模型
深度探索C++对象模型 什么是C++对象模型: 语言中直接支持面向对象程序设计的部分. 对于各个支持的底层实现机制. 抽象性与实际性之间找出平衡点, 需要知识, 经验以及许多思考. 导读 这本书是C+ ...
- 《深度探索C++对象模型》读书笔记(一)
前言 今年中下旬就要找工作了,我计划从现在就开始准备一些面试中会问到的基础知识,包括C++.操作系统.计算机网络.算法和数据结构等.C++就先从这本<深度探索C++对象模型>开始.不同于& ...
- C++的黑科技(深入探索C++对象模型)
周二面了腾讯,之前只投了TST内推,貌似就是TST面试了 其中有一个问题,“如何产生一个不能被继承的类”,这道题我反反复复只想到,将父类的构造函数私有,让子类不能调用,最后归结出一个单例模式,但面试官 ...
- 深入探索C++对象模型(一)
再读<深入探索C++对象模型>笔记. 关于对象 C++在加入封装后(只含有数据成员和普通成员函数)的布局成本增加了多少? 答案是并没有增加布局成本.就像C struct一样,memeber ...
随机推荐
- one 策略模式 strategy
--读书笔记 定义 策略模式--定义算法簇,分别封装起来,让他们之间可以互相替换,此模式让算法的变化独立于使用算法的客户.(看不懂的话,往下,有人话版/我自己的解释) 相关原则 > 1,变化单独 ...
- springboot-mybatis 批量insert
springboot mybatis 批量insert 操作 直接上代码: 1.首先要在pom.xml中导入包: 略...... 2.springboot mybatis配置: package com ...
- 字符串的缺点以及StringBuilder
字符串的缺点: String 对象是不可改变的.每次使用 System.String 类中的方法之一时,都要在内存中创建一个新的字符串对象,这就需要为该新对象分配新的空间.在需要对字符串执行重复修改的 ...
- python学习笔记 python实现k-means聚类
# -*- coding: utf-8 -*- """ Created on Thu Mar 16 14:52:58 2017 @author: Jarvis " ...
- 认识 Less
CSS(层叠样式表)是一项出色的技术,它使得网页的表现与内容完全分离,使网站维护工作变得更容易,不会因为内容的改变而影响表现,也不会因为表现的改变而影响内容. 作为一门标记性语言,CSS 的先天性优点 ...
- Android视频编辑SDK--RDVECore来自锐动的无UI,高度抽象化API
1 RDVECore功能概述 RDVECore是锐动推出的无UI,高度抽象化API的视频编辑SDK,支持以下功能: 1.1 丰富的编辑功能 RDVECore包含了丰富的基础功能,对于编辑中的视频.图片 ...
- 安装vue-cli时出现的错误,cmd 卡住
今天在构建vue 时遇到个问题, cmd 执行 vue init webpack my-project 时 , 出现如下乱码, 然后 页面 卡住在 项目说明 ,操作不了, 最后发现是 nodejs 版 ...
- 35.Linux-分析并制作环形缓冲区
在上章34.Linux-printk分析.使用printk调试驱动里讲述了: printk()会将打印信息存在内核的环形缓冲区log_buf[]里, 可以通过dmesg命令来查看log_buf[] 1 ...
- 防止SSH自动断线
在连接远程SSH服务的时候,经常会发生长时间后的断线,或者无响应(无法再键盘输入). 总体来说有两个方法: 1.依赖ssh客户端定时发送心跳. putty.SecureCRT.XShell都有这个功能 ...
- OGNL简介
OGNL 一:OGNL简介 OGNL的全称是Object Graph Navigation Language即对象导航语音.它是一个开源项目,工作在视图层,用来取代页面中的java脚本.简化数据 ...