这篇文章我要简单地讲解下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++对象内存布局的更多相关文章

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

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

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

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

  3. c++对象内存布局的理解

    我对c++对象内存布局的理解   引言 结合网上的一些资料,通过自己的一番摸索,得出了一点个人见解.现在写下来,希望与各位同学共同探讨,共同进步. 以下所有代码均是在VS2012下测试. 一个普通的基 ...

  4. 好文章系列C/C++——图说C++对象模型:对象内存布局详解

    注:收藏好文章,得出自己的笔记,以查漏补缺!     ------>原文链接:http://blog.jobbole.com/101583/ 前言 本文可加深对C++对象的内存布局.虚表指针.虚 ...

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

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

  6. 【转载】图说C++对象模型:对象内存布局详解

    原文: 图说C++对象模型:对象内存布局详解 正文 回到顶部 0.前言 文章较长,而且内容相对来说比较枯燥,希望对C++对象的内存布局.虚表指针.虚基类指针等有深入了解的朋友可以慢慢看.本文的结论都在 ...

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

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

  8. C++对象内存布局测试总结

    C++对象内存布局测试总结 http://hi.baidu.com/����/blog/item/826d38ff13c32e3a5d6008e8.html 上文是半年前对虚函数.虚拟继承的理解.可能 ...

  9. C++对象内存布局 (二)

    在上一篇文章中讨论了C++单一一般继承的对象内存布局http://www.cnblogs.com/uangyy/p/4621561.html 接下来继续讨论第二种情况: 2.单一的虚拟继承:有成员变量 ...

随机推荐

  1. 提高HTML5 canvas性能的几种方法

    简介 HTML5 canvas 最初起源于苹果(Apple)的一项实验,现在已经成为了web中受到广泛支持的2D快速模式绘图(2Dimmediate mode graphic)的标准.许多开发者现在利 ...

  2. docker(4):coreos+docker+rancher真厉害

    http://blog.csdn.net/freewebsys/article/category/3103827

  3. MOGODB REDIS

    http://www.cnblogs.com/huangxincheng/ http://blog.csdn.net/opens_tym/article/details/9832301 http:// ...

  4. IOS中类和对象还有,nil/Nil/NULL的区别

    转自:http://blog.sina.com.cn/s/blog_5fb39f910101akm1.html 类与对象的概念 类是对同一类事物高度的抽象,类中定义了这一类对象所应具有的静态属性(属性 ...

  5. php 日期

    获取两个日期之间的间隔天列表: $begin = new DateTime( '2012-08-0' );$end = new DateTime( '2012-08-31' );$end = $end ...

  6. 一步一步实现iOS微信自动抢红包

    微信红包 前言:最近笔者在研究iOS逆向工程,顺便拿微信来练手,在非越狱手机上实现了微信自动抢红包的功能.   此教程所需要的工具/文件 yololib class-dump dumpdecrypte ...

  7. solr 相似查询-MoreLikeThis

    参考文档: MoreLikeThis MoreLikeThisHandler 在solr中有两种方式实现MoreLikeThis:第一种:SearchHandler中的MoreLikeThisComp ...

  8. IOS 读取本地的Json/plist 文件

    一.一般本地可以存储轻量级数据存储 plist  这个主要是操作字典 方法如下: NSString * sampleFile= [[[NSBundle mainBundle] bundlePath] ...

  9. oracle 11g ora-01843 无效月份

    PS:进入注册表--regedit--HKEY_LOCAL_MACHINE--SOFTWARE--ORACLE-KEY_OraDb11g_home1---NLS_LANG(双击此项设置的键值为AMER ...

  10. 获取客户端的IP地址

    /// <summary> /// 获取客户端的IP地址 /// </summary> /// <returns></returns> public s ...