一、封装模型的内存布局

常见类对象的成员可能包含以下元素:

内建类型、指针、引用、组合对象、虚函数。

另一个角度的分类:

数据成员:静态、非静态

成员函数:静态、非静态、虚函数

1.仅包含内建类型的场合:

class T
{
int data1;
char data2;
double data3;
};

  

类中的内建类型按照声明的顺序在内存中连续存储,并且分配的大小由内建类型本身的大小决定(依赖机器),布局受字节对齐影响(本篇不讨论字节对齐)

2.包含指针和引用的场合:

class T
{
int data1;
char data2;
double data3;
int& ri1;//需要构造函数
int* rp1;
int (*pf)();
};

  

存储方式同1的场合,不同点为指针和引用通常为固定大小(32位机器4字节、64位机器8字节)。

有关引用:个人理解的引用就是懒人专用指针,取地址又间地址是很麻烦的操作,于是出现了自动取址又间址的指向常量的常指针

在类中声明可以测出固定字节大小,所以也是占用固定的字节大小。

3.包含组合对象的场合:

class Q
{
int a;
int b;
}; class T
{
int data1;
Q q;
double data2;
};

 

内存布局图示(本篇以及后续篇使用的环境为 32位Win7, VS2008):

再来看一下地址:

结论:(显而易见就不解释了)

类对象最终被解释成内建类型,布局依然按照声明的顺序,并且对象布局在内存中依然是连续的

4.在3的场合添加虚函数的场合

class Q
{
virtual void fun(){}
int a;
int b;
}; class T
{
virtual void fun(){}
int data1;
Q q;
double data2;
};

内存布局图示

通过程序输出看一下

typedef void (*PF)();

int main()
{
T t;
PF pf1, pf2; cout<<"vfptr的地址"<<(int*)&t<<endl;
cout<<"vftable的地址"<<(int*)*(int*)&t<<endl;
cout<<"通过vftable调用类T的fun函数: ";
pf1 = (PF)*(int*)*(int*)&t;
pf1(); cout<<"通过vftable调用类Q的fun函数: ";
pf2 = (PF)*(int*)*(int*)&t.q;
pf2(); return 0;
}

输出图示

推理证明:

1.取t的地址强转成(int*)类型输出以后得到的地址 == 取t的vfptr的地址(调试窗口第一行): 虚函数指针被放在对象布局的首地址位置

2.因为(int*)&t == vfptr,那么*vfptr得到的是虚函数表的首地址。

(int*)*vfptr,把虚函数表的首地址强转成(int*)的地址 == t对象的__vftable的虚函数表的地址(调试窗口第四行行):虚函数指针指向虚函数表

3.vftable的首地址到vftable的第一个函数的地址中间相差很多空间:虚函数表还承担了虚函数以外的内容

什么内容也会放在虚函数表中呢?

虚函数表用来实现多态,多态意味着类型上的模糊,模糊以后必须有东西来记录自己的老本,否则无法实现另外一个东西——RTTI

结论:

在包含虚函数的场合多了一个vfptr,它是一个const指针,位于类布局中的首位置,指向了虚函数表,虚函数表包含了虚函数地址,通过虚函数地址访问虚函数。

并且虚函数表的首地址存在了本类的类型信息,用于实现RTTI。

5.包含了static的场合

static的特性众所周知,从调试窗口观察变量并不能得出什么结论,我们先列出几条特性:

1.static成员为整个类共有的属性

2.static函数不包含this指针

3.static成员不能访问nonstatic成员

初步结论:

内存对象模型中对static作了隔离处理(不是所有对象具有的),static自己独霸一方。

通过以上5条现在来构建C++的封装模型:

有关普通的成员函数

所谓类,就是自己圈定了一个域名,所以在内存中的代码区也圈定了自己的域,普通的成员函数放在那里。

有关静态成员函数

在代码区中圈定的类域名中的圈定一个static区域,思路依然是独霸一方。

有关构造函数

由于构造函数的特殊性,所以在代码区拥有一个自己的构造代码区域。

现在又有了一个更完整的模型:

假定读者已经了解堆/栈/静态区和常量区/代码区

根据上图我们得到一些结论

1.类最终被解释内建类型(内建类型过了编译期以后,都不复存在,只是编译期的解读方式而已)

2.内建类型按照声明的次序顺序存储

3.存在虚函数的场合,会生成vfptr,并且vfptr->vtable->function()

4.静态成员被单独对待、数据只有一份拷贝,函数被放到static区域。

5.Type Infomation被放到vftable中

二、封装模型的构造过程

1.静态是编译期决定的,所有对象共有的数据拷贝,优先创建。

2.进入构造函数,优先创建vfptr和vftable,也就是优先构造虚函数部分

3.其次按照声明的顺序构造数据成员。

我们可以使用逗号表达式来干一些有意思的事情。

事先我们需要定义

typedef void (*PF)();
PF pf = NULL;

class Q
{
public:
Q():b((cout<<"b constructing\n", 1)), a((cout<<"a constructing\n", 2)){}//组合对象的初始化顺序,注意初始化列表写的顺序是和声明的顺序相反的 virtual void fun(){cout<<"Q::f"<<endl;}
int a;
int b;
}; class T
{
public:
T():data1(((pf =(PF)*(int*)*(int*)this, pf()), cout<<"data1 constructing\n", 1)), data2((cout<<"data2 constructing\n", 2)){}//data2的构造使用了简单的逗号表达式
  //data1的初始化嵌套了一层逗号表达式,结构其实是data1((为函数指针pf赋值, 调用pf), 打印data1构造中, 数值)
virtual void fun(){cout<<"T::f"<<endl;}
int data1;
Q q;
double data2;
static int sdata1;
}; int T::sdata1 = (cout<<"sdata1 constructing\n", 10);//用来指定静态变量的初始化顺序

以下是程序运行的结果:

静态--虚函数表--声明次序初始化。

从C++对象内存布局和构造过程来具体分析C++中的封装、继承、多态的更多相关文章

  1. c++ 对象内存布局详解

    今天看了的,感觉需要了解对象内存的问题.参考:http://blog.jobbole.com/101583/ 1.何为C++对象模型? 引用<深度探索C++对象模型>这本书中的话: 有两个 ...

  2. 浅析GCC下C++多重继承 & 虚拟继承的对象内存布局

    继承是C++作为OOD程序设计语言的三大特征(封装,继承,多态)之一,单一非多态继承是比较好理解的,本文主要讲解GCC环境下的多重继承和虚拟继承的对象内存布局. 一.多重继承 先看几个类的定义: 01 ...

  3. 使用sos查看.NET对象内存布局

    前面我们图解了.NET里各种对象的内存布局,我们再来从调试器和clr源码的角度来看一下对象的内存布局.我写了一个测试程序来加深对.net对象内存布局的了解: using System; using S ...

  4. Java对象内存布局

    本文转载自Java对象内存布局 导语 首先直接抛出问题 Unsafe.getInt(obj, fieldOffset)中的fieldOffset是什么, 类似还有compareAndSwapX(obj ...

  5. JVM-对象及对象内存布局

    目录 前言 类与对象 对象类二分模型 对象 对象内存布局 JOL工具 对象头 Mark Word 类型句柄 对象头与锁膨胀 无锁 偏向锁 轻量级锁 重量级锁 重量级锁降级 实例数据 填充 对象生命周期 ...

  6. JVM之对象创建、对象内存布局、对象访问定位

    对象创建 类加载过后可以直接确定一个对象的大小 对象栈上分配是通过逃逸分析判定.标量替换实现的,即把不存在逃逸的对象拆散,将成员变量恢复到基本类型,直接在栈上创建若干个成员变量 选择哪种分配方式由Ja ...

  7. 图文详解Java对象内存布局

    作为一名Java程序员,我们在日常工作中使用这款面向对象的编程语言时,做的最频繁的操作大概就是去创建一个个的对象了.对象的创建方式虽然有很多,可以通过new.反射.clone.反序列化等不同方式来创建 ...

  8. JVM 系列(4)一看就懂的对象内存布局

    请点赞关注,你的支持对我意义重大. Hi,我是小彭.本文已收录到 GitHub · AndroidFamily 中.这里有 Android 进阶成长知识体系,有志同道合的朋友,关注公众号 [彭旭锐] ...

  9. 图说C++对象模型:对象内存布局详解

    0.前言 文章较长,而且内容相对来说比较枯燥,希望对C++对象的内存布局.虚表指针.虚基类指针等有深入了解的朋友可以慢慢看. 本文的结论都在VS2013上得到验证.不同的编译器在内存布局的细节上可能有 ...

随机推荐

  1. CF 444A(DZY Loves Physics-低密度脂蛋白诱导子图)

    A. DZY Loves Physics time limit per test 1 second memory limit per test 256 megabytes input standard ...

  2. mysql 新增 删除用户和权限分配

    请一定安此步骤来创建新的用户. 1. 新增用户 mysql>insert into mysql.user(Host,User,Password) values("localhost&q ...

  3. Activity(三)

    常用控件 EditView.TextView.Button 设置layout,在fragment_main.xml配置控件 配置可编辑文本控件factorOne.factorTwo,显示文本控件sym ...

  4. javascript设计模式——Module

    Module模式是提供公有和私有方法的代码块,有利于封装组织代码,可减少变量及函数名与其它模块的冲突. 推荐阅读: http://www.adequatelygood.com/JavaScript-M ...

  5. asp.net session的原理

    session,会话,指的就是用户在浏览某个网站时,从进入网站到浏览器关闭所经过的这段时间,也就是用户浏览这个网站所花费的时间. A用户和C服务器建立连接时所处的Session同B用户和C服务器建立连 ...

  6. (六)Android中Service通信

    一.启动Service并传递参数 传递参数时只需在startService启动的Intent中传入数据便可,接收参数时可在onStartCommand函数中通过读取第一个参数Intent的内容来实现 ...

  7. Java中两种实现多线程方式的对比分析

    本文转载自:http://www.linuxidc.com/Linux/2013-12/93690.htm#0-tsina-1-14812-397232819ff9a47a7b7e80a40613cf ...

  8. 卸载cloudera manager

    卸载Cloudera-Manager sudo /usr/share/cmf/uninstall-cloudera-manager.sh 一直选择Yes 就好 卸载完成后,它会问你是否要将databa ...

  9. php将unicode编码转为utf-8方法

    介绍 在前端开发中,为了让中文在不同的环境下都能很好的显示,一般是将中文转化为unicode格式,即\u4f60,比如:"你好啊"的 unicode编码为"\u4f60\ ...

  10. sass颜色

    1只定义一次颜色 {优点:可以给变量赋予不同的值: {缺点:变量名称更改与变量值混乱: 2变浅加深 /*颜色函数*/ .warning-box { background-color:lighten($ ...