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.单一的虚拟继承:有成员变量 ... 
随机推荐
- MPI编程简单介绍
			第三章MPI编程 3.1 MPI简单介绍 多线程是一种便捷的模型,当中每一个线程都能够訪问其他线程的存储空间.因此,这样的模型仅仅能在共享存储系统之间移植.一般来讲,并行机不一定在各处理器之间共享存储 ... 
- 关于self.用法的一些总结
			转自:http://www.cocoachina.com/bbs/read.php?tid=12850&page=1 最近有人问我关于什么时候用self.赋值的问题, 我总结了一下, 发出来给 ... 
- 表ADT
			表一般不用简单数组来实现,通常将其实现为链表.在链表中要不要使用表头则属于个人兴趣问题.在下面的例程中我们都使用表头. 按照C的约定,作为类型的List(表)和Position(位置)以及函数的原型都 ... 
- WWH学习模式
			WWH是"What+Why+How"的简称,是对学习方法最完美的概括."如果不按照WWH这种模式来教学,90%的结果是老师没教好,学生学不好." 1.What( ... 
- IOS开发之--异常处理--使用try 和 catch 来捕获错误。
			一个搞java的老板问我会不会try catch 我说不会 学这么久也没听周围朋友用这个 因为苹果控制台本来就可以打印异常 特此研究一下. 1.try catch: 是捕获异常代码段 特点:对 ... 
- HTML超出文本显示省略号...[text-overflow]
			需要对div或者span同时应用Css: text-overflow:ellipsis; white-space:nowrap; overflow:hidden; 即可实现所想要得到的溢出文本显示省略 ... 
- Shell学习笔记 - 正则表达式
			一.正则表达式是什么? 正则表达式是用于描述字符排列和匹配模式的一种语法规则.它主要用于字符串的模式分割.匹配.查找及替换操作. 二.正则表达式与通配符 1. 正则表达式 用来在文件中匹配符合条件的字 ... 
- cxGrid使用汇总1
			这些都不是原创,只是平时收集到资料然后整理的,有些可能百度一下到处都是而且还大同小异也有些不是很好找,现在贴出来希望给那些用到cxGrid的人会有所帮助 1. 去掉cxGrid中台头的Box 解决:在 ... 
- POJ 1733 Parity game (并查集)
			Parity game Time Limit: 1000MS Memory Limit: 65536K Total Submissions: 6816 Accepted: 2636 Descr ... 
- 房间声学原理与Schroeder混响算法实现
			一.混响时间的计算与预测 所谓混响就是声音的直达声与反射声很紧凑的重合在一起时人耳所听到的声音,这个效果在语音的后期处理时特别有用.能产生混响最常见的场景就是房间内,尤其是空旷的房间中. 混响有直达声 ... 
