一、封装模型的内存布局

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

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

另一个角度的分类:

数据成员:静态、非静态

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

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. [AS3]as3用ByteArray来对SWF文件编码加密实例参考

    [AS3]as3用ByteArray来对SWF文件编码加密实例参考,简单来说,就是将 swf 以 binary 的方式读入,并对 ByteArray 做些改变,再重新存成 swf 档.这个作业当然也可 ...

  2. 实用的JavaScript技巧、窍门和最佳实践

    JavaScript是世界上第一的编程语言,它是Web的语言,是移动混合应用(mobile hybrid apps)的语言(比如 PhoneGap或者 Appcelerator),是服务器端的语言(比 ...

  3. 【位运算DFS/DLX】【HDU1426】【数独】

    题意:标准的一道数独题 DFS做法: 将横纵九宫格里的数字用位运算状态压缩,且可以通过逻辑或来确定总共有哪些数字被选择了,很方便也很快,代码如下 #include <cstdio> #in ...

  4. APP安全测评checklist

    leader不要打我啊,我要借用一下我组app的安全测评检查方案,这些最基本的安全防范措施应该是每个app都要注意的吧: 对了,首先,你的app得先混淆啊~:AndroidStudio 混淆打包 先来 ...

  5. 转:消除SDK更新时的“https://dl-ssl.google.com refused”错误

    消除SDK更新时,有可能会出现这样的错误: Download interrupted: hostname in certificate didn't match: <dl-ssl.google. ...

  6. getpwuid()函数

    linux getpwuid 得到指定用户信息 和系统数据相关的passwd 结构 和系统数据相关的一个结构passwd定义如下 /* The passwd structure. */ struct ...

  7. Spring学习之切入点表达式

    链接地址:http://jinnianshilongnian.iteye.com/blog/1415606

  8. mysql中select distinct的用法

    在使用mysql时,有时需要查询出某个字段不重复的记录,虽然mysql提供有distinct这个关键字来过滤掉多余的重复记录只保留一条,但 往往只用它来返回不重复记录的条数,而不是用它来返回不重记录的 ...

  9. 【转】C++虚函数解析

    本文转自陈皓大叔(左耳朵耗子)的博客www.coolshell.com. 文章是很久之前所写,去年还在写C++时有幸拜读,现在想起来还是相当有价值一转的,如果有一定C++基础(特别是读过<深度探 ...

  10. HibernateTransactionManager 和 hibernateTemplate的区别

    在applicationContext.xml中有如下配置: <bean id="hibernateTemplate" class="org.springframe ...