这篇文章我要简单地讲解下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. 小白日记52:kali渗透测试之Web渗透-HTTPS攻击(Openssl、sslscan、sslyze、检查SSL的网站)

    HTTPS攻击 全站HTTPS正策划稿那位潮流趋势 如:百度.阿里 HTTPS的作用 CIA 解决的是信息传输过程中数据被篡改.窃取 [从中注入恶意代码,多为链路劫持] 加密:对称.非对称.单向 HT ...

  2. Tfs服务器迁移(更改IP)后客户端(vs2013)配置方法

    一.前言 公司开发项目需要进驻客户现场,局域网中的tfs代码服务器有时会有修改ip的情况发生.服务器ip修改后在vs2013中更改服务器ip地址非常麻烦,有些时候需要将代码重新进行映射.多次试验后将可 ...

  3. ios-UIPickerView基本使用

    #import "ViewController.h" @interface ViewController ()<UIPickerViewDataSource,UIPicker ...

  4. flexpaper 在线观看 PPT,PDF,DOC等文档

    0.安装环境.可以参考http://www.cnblogs.com/star-studio/archive/2011/12/09/2281807.html     百度关键字 仿百度文库方案 1.借用 ...

  5. python pdb调试

    在交互环境中通常使用pdb.run来调试: import pdb def pdb_test(arg): for i in range(arg): print(i) return arg pdb.run ...

  6. page61-将二分查找重写为一段面向对象的程序

    1 将二分查找重写为一段面向对象的程序 (用于在整数集合中进行查找的一种抽象数据类型) public class StaticSETofInts [API] StaticSETofInts(int[] ...

  7. Linux+Apache+Php+Oracle 基础环境搭建

    前言:                       不能保证100%一次通过,每个系统都会或多或少有区别,如果缺少某些依赖包的话,还请见谅   1.安装Apache需要编译安装的包,各官方网站下载   ...

  8. vs克隆新建团队项目

    团队资源——克隆,完成后打开: 在解决方案资源中,添加——新建项目——web——ASP.NET web应用程序——空的,并将其设为启动项目:

  9. codeforces 677D D. Vanya and Treasure(二维线段树)

    题目链接: D. Vanya and Treasure time limit per test 1.5 seconds memory limit per test 256 megabytes inpu ...

  10. iOS小技巧 - 和屏幕等宽的Table分割线

    前言 因为本人也是学习iOS才一个多月,在写程序的过程中经常会遇到一些看似应该很简单,但是要解决好却要知道一点小trick的问题. 因此后面会陆续记一些这类问题,一来加深印象,二来也可以做个备忘录. ...