c++对象内存布局
这篇文章我要简单地讲解下c++对象的内存布局,虽然已经有很多很好的文章,不过通过实现发现有些地方不同的编译器还是会有差别的,希望和大家交流。
在没有用到虚函数的时候,C++的对象内存布局和c语言的struct是一样的,这个比较容易理解,本文只对有虚函数的情况作分析,大致可以从以下几个方面阐述,
1. 单一继承
2. 多重继承
3. 虚继承
下面循序渐进的逐个分析,环境是ubuntu 12.04.3 LTS+gcc4.8.1
单一继承
为了实现运行时多态,虚函数的地址并不能在编译期决定,需要运行时通过虚函数表查找实际调用的虚函数地址。单一继承主要要弄明白两个问题:
1. 虚函数表在哪里?
2. 基类和派生类的虚函数是按照怎样的规则组织的?

类结构代码如下:
class base{
public:
base(int bv):bval(bv){};
virtual void base_f(){cout<<"base::f()"<<endl;}
virtual void base_g(){cout<<"base::g()"<<endl;}
private:
int bval;
};
class derived: public base{
public:
derived(int bv, int dv):base(bv), dval(dv){};
void base_f(){cout<<"derived::f()"<<endl;}
virtual void derived_h(){cout<<"derived::h()"<<endl;}
private:
int dval;
};
使用以下测试代码查看基类和派生类的内存空间:
base b();
FUN fun;
int **pvtab = (int**)&b;
cout<<"[0]:base->vptr"<<endl;
for(int i=; i<; i++){
fun = (FUN)pvtab[][i];
cout<<" "<<i<<" ";
fun();
}
cout<<" 2 "<<pvtab[][]<<endl;
cout<<"[1]:bval "<<(int)pvtab[]<<endl;
derived d(, );
pvtab = (int**)&d;
cout<<"[0]:derived->vptr "<<endl;
for(int i=; i<; i++){
fun = (FUN)pvtab[][i];
cout<<" "<<i<<" ";
fun();
}
cout<<" 3 "<<pvtab[][]<<endl;
cout<<"[1]:bval "<<(int)pvtab[]<<endl;
cout<<"[2]:dval "<<(int)pvtab[]<<endl;
运行结果:
[0]:base->vptr
0 base::f()
1 base::g()
2 1919247415
[1]:bval 10
[0]:derived->vptr
0 derived::f()
1 base::g()
2 derived::h()
3 0
[1]:bval 10
[2]:dval 100
用图表示就是这样的(本文中属于同一个类的函数和数据成员我都用同一种颜色表示,以便区分)。

结果可以看出:
1. 虚函数表指针在对象的第一个位置。
2. 成员变量根据其继承和声明顺序依次排列。
3. 虚函数根据继承和声明顺序依次放在虚函数表中,派生类中重写的虚函数在原来位置更新,不会增加新的函数。
4. 派生类虚函数表最后一个位置是NULL,但是基类是一个非空指针(红色字体显示),我不明白这个位置在gcc中的作用,有知道的朋友可以告诉我,非常感谢!
多重继承
如果加上多重继承,情况会有那么一点点复杂了,多个基类的虚函数如何组织才能使用任意一个基类都可以达到多态效果?

类结构代码如下:
class base1{
public:
base1(int bv):bval1(bv){};
virtual void base_f(){cout<<"base1::f()"<<endl;}
virtual void base1_g(){cout<<"base1::g()"<<endl;}
private:
int bval1;
};
class base2{
public:
base2(int bv):bval2(bv){};
virtual void base_f(){cout<<"base2::f()"<<endl;}
virtual void base2_g(){cout<<"base2::g()"<<endl;}
private:
int bval2;
};
class derived: public base1, public base2{
public:
derived(int bv1, int bv2, int dv):base1(bv1),base2(bv2), dval(dv){};
virtual void base_f(){cout<<"derived::f()"<<endl;}
virtual void derived_h(){cout<<"derived::h()"<<endl;}
private:
int dval;
};
测试代码查看内存布局:
derived d(, , );
FUN fun;
int **pvtab = (int**)&d;
cout<<"[0]:base1->vptr "<<endl;
for(int i=; i<; i++){
fun = (FUN)pvtab[][i];
cout<<" "<<i<<" ";
fun();
}
cout<<" 3 "<<pvtab[][]<<endl;
cout<<"[1]:bval1 "<<(int)pvtab[]<<endl;
cout<<"[2]:base2->vptr "<<endl;
for(int i=; i<; i++){
fun = (FUN)pvtab[][i];
cout<<" "<<i<<" ";
fun();
}
cout<<" 2 "<<pvtab[][]<<endl;
cout<<"[3]:bval2 "<<(int)pvtab[]<<endl;
cout<<"[4]:dval "<<(int)pvtab[]<<endl;
运行结果如下:
[0]:base1->vptr
0 derived::f()
1 base1::g()
2 derived::h()
3 -8
[1]:bval1 10
[2]:base2->vptr
0 derived::f()
1 base2::g()
2 0
[3]:bval2 100
[4]:dval 1000

总结:
1. 每个基类有单独的虚函数表,子类的虚函数放在第一个基类的虚函数表中。
2. 基类内存空间按基类的声明顺序依次排列
3. 如果多个基类之间有同名虚函数,派生类重写时会覆盖所有基类的该函数。这里插一句,如果没有覆盖,不同基类之间的同名函数是会产生二义性的,使用时必须指定哪个类的函数。
4. 第一个基类的虚函数表最后一个位置仍不为空(红色字体),作用不明。
虚继承
虚继承需要保证基类在派生类中只有一份,所以内存布局比多重继承又复杂那么一点点,我们以最典型的菱形继承为例子介绍。

类结构代码如下
class base{
public:
base(int bv):bval(bv){};
virtual void base_f(){cout<<"base::f()"<<endl;}
virtual void base_t(){cout<<"base::t()"<<endl;}
private:
int bval;
};
class base1:virtual public base{
public:
base1(int bv, int bv1):base(bv), bval1(bv1){};
void base_f(){cout<<"base1::f()"<<endl;}
virtual void base1_g(){cout<<"base1::g()"<<endl;}
virtual void base1_k(){cout<<"base1::k()"<<endl;}
private:
int bval1;
};
class base2: virtual public base{
public:
base2(int bv, int bv2):base(bv), bval2(bv2){};
void base_f(){cout<<"base2::f()"<<endl;}
virtual void base2_g(){cout<<"base2::g()"<<endl;}
virtual void base2_k(){cout<<"base2::k()"<<endl;}
private:
int bval2;
};
class derived: public base1, public base2{
public:
derived(int bv, int bv1, int bv2, int dv):base(bv), base1(bv, bv1),base2(bv, bv2), dval(dv){};
void base_f(){cout<<"derived::f()"<<endl;}
void base1_g(){cout<<"derived::base1_g()"<<endl;}
void base2_g(){cout<<"derived::base2_g()"<<endl;}
virtual void derived_h(){cout<<"derived::h()"<<endl;}
private:
int dval;
}
测试代码查看内存结构
derived d(, , , );
FUN fun = NULL;
int **pvtab = (int**)&d;
cout<<"[0]:base1->vptr "<<endl;
for(int i=; i<; i++){
fun = (FUN)pvtab[][i];
cout<<" "<<i<<" ";
fun();
}
cout<<" 5 "<<pvtab[][]<<endl;
cout<<"[1]:bval1 "<<(int)pvtab[]<<endl; cout<<"[2]:base2->vptr "<<endl;
for(int i=; i<; i++){
fun = (FUN)pvtab[][i];
cout<<" "<<i<<" ";
fun();
}
cout<<" 3 "<<pvtab[][]<<endl;
cout<<"[3]:bval2 "<<(int)pvtab[]<<endl;
cout<<"[4]:dval "<<(int)pvtab[]<<endl;
cout<<"[5]:base->vptr "<<endl;
for(int i=; i<; i++){
fun = (FUN)pvtab[][i];
cout<<" "<<i<<" ";
fun();
}
cout<<" 2 "<<pvtab[][]<<endl;
运行结果:
[0]:base1->vptr
0 derived::f()
1 derived::base1_g()
2 base1::k()
3 derived::base2_g()
4 derived::h()
5 134516512
[1]:bval1 100
[2]:base2->vptr
0 derived::f()
1 derived::base2_g()
2 base2::k()
3 0
[3]:bval2 1000
[4]:dval 10000
[5]:base->vptr
0 derived::f()
1 base::t()
2 134517004

结果可以看出:
1. 直接非虚基类按声明顺序依次排列,然后是派生类,这个和多重继承是一样的。
2. 派生类的虚函数仍然放在第一个直接非虚基类的虚函数表中.
3. 虚基类放在最后,只有一份。
c++对象内存布局的更多相关文章
- 图说C++对象模型:对象内存布局详解
0.前言 文章较长,而且内容相对来说比较枯燥,希望对C++对象的内存布局.虚表指针.虚基类指针等有深入了解的朋友可以慢慢看. 本文的结论都在VS2013上得到验证.不同的编译器在内存布局的细节上可能有 ...
- c++ 对象内存布局详解
今天看了的,感觉需要了解对象内存的问题.参考:http://blog.jobbole.com/101583/ 1.何为C++对象模型? 引用<深度探索C++对象模型>这本书中的话: 有两个 ...
- c++对象内存布局的理解
我对c++对象内存布局的理解 引言 结合网上的一些资料,通过自己的一番摸索,得出了一点个人见解.现在写下来,希望与各位同学共同探讨,共同进步. 以下所有代码均是在VS2012下测试. 一个普通的基 ...
- 好文章系列C/C++——图说C++对象模型:对象内存布局详解
注:收藏好文章,得出自己的笔记,以查漏补缺! ------>原文链接:http://blog.jobbole.com/101583/ 前言 本文可加深对C++对象的内存布局.虚表指针.虚 ...
- 使用sos查看.NET对象内存布局
前面我们图解了.NET里各种对象的内存布局,我们再来从调试器和clr源码的角度来看一下对象的内存布局.我写了一个测试程序来加深对.net对象内存布局的了解: using System; using S ...
- 【转载】图说C++对象模型:对象内存布局详解
原文: 图说C++对象模型:对象内存布局详解 正文 回到顶部 0.前言 文章较长,而且内容相对来说比较枯燥,希望对C++对象的内存布局.虚表指针.虚基类指针等有深入了解的朋友可以慢慢看.本文的结论都在 ...
- 浅析GCC下C++多重继承 & 虚拟继承的对象内存布局
继承是C++作为OOD程序设计语言的三大特征(封装,继承,多态)之一,单一非多态继承是比较好理解的,本文主要讲解GCC环境下的多重继承和虚拟继承的对象内存布局. 一.多重继承 先看几个类的定义: 01 ...
- C++对象内存布局测试总结
C++对象内存布局测试总结 http://hi.baidu.com/����/blog/item/826d38ff13c32e3a5d6008e8.html 上文是半年前对虚函数.虚拟继承的理解.可能 ...
- C++对象内存布局 (二)
在上一篇文章中讨论了C++单一一般继承的对象内存布局http://www.cnblogs.com/uangyy/p/4621561.html 接下来继续讨论第二种情况: 2.单一的虚拟继承:有成员变量 ...
随机推荐
- Js 数组——filter()、map()、some()、every()、forEach()、lastIndexOf()、indexOf()
filter(): 语法: var filteredArray = array.filter(callback[, thisObject]); 参数说明: callback: 要对每个数组元素执行 ...
- NHibernate教程
NHibernate教程 一.NHibernate简介 在今日的企业环境中,把面向对象的软件和关系数据库一起使用可能是相当麻烦.浪费时间的.NHibernate是一个面向.Net环境的对象/关系数据库 ...
- spring事务的传播特性
所谓事务传播行为就是多个事务方法相互调用时,事务如何在这些方法间传播.Spring 支持 7 种事务传播行为: PROPAGATION_REQUIRED 如果当前没有事务,就新建一个事务,如果已经存在 ...
- 关于android在Service中弹出Dialog对话框
在创建好AlertDialog类型对象后,要 dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT); 同时还 ...
- 结合源码看nginx-1.4.0之nginx全局变量ngx_cycle初始化详解
目录 0. 摘要 1. ngx_cycle_t结构设计 2. ngx_cycle_t数据结构 3. nginx全局变量ngx_cycle初始化 4. 小结 5. 参考资料 0. 摘要 Nginx核心的 ...
- [Arduino] 逗号分隔文本到数组的两种方法
以下是今日练习通过逗号来分割字符数组/字符串的2个例子和方法" 1.通过indexOf函数 /* *Splitsplit sketch *split a comma-separated st ...
- 控制Input框输入的为大写字母
本来我的想法是Jquery来控制的,万万没想到...用Css就可以实现!!!! .toUp input{ text-transform:uppercase; } 感谢: http:// ...
- Unity3d之Shader编程:子着色器、通道与标签的写法 & 纹理混合
一.子着色器 Unity中的每一个着色器都包含一个subshader的列表,当Unity需要显示一个网格时,它能发现使用的着色器,并提取第一个能运行在当前用户的显示卡上的子着色器. 我们知道,子着色器 ...
- (一)u-boot2013.01.01 for TQ210:《Uboot简介》
一直想写一个s5pv210硬件平台的u-boot的移植文档,但一直都忙着没时间写.先写一些u-boot的脚本分析吧,包括makefile,mkconfig,config.mk,主要侧重于语法句意的分析 ...
- ubuntu创建、删除文件及文件夹,强制清空回收站方法
mkdir 目录名 => 创建一个目录 rmdir 空目录名 => 删除一个空目录 rm 文件名 文件名 => 删除一个文件或多个文件 rm –rf 非 ...