这篇文章我要简单地讲解下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. oc-31-多对象的内存管理

    在每个OC对象内部,都专门有8个字节的存储空间来存储引用计数器. 引用计数器的常见操作 retain消息:堆内存中对象的计数器变量 +(该方法返回对象本身,要想计数器变量加1就要调用对象的retain ...

  2. C#使用Monitor类、Lock和Mutex类进行多线程同步

    在多线程中,为了使数据保持一致性必须要对数据或是访问数据的函数加锁,在数据库中这是很常见的,但是在程序中由于大部分都是单线程的程序,所以没有加锁的必要,但是在多线程中,为了保持数据的同步,一定要加锁, ...

  3. JS 解析Xml

    loadXML = function (xmlString) { var xmlDoc = null; //判断浏览器的类型 //支持IE浏览器 if (!window.DOMParser & ...

  4. IOS 应用中从竖屏模式强制转换为横屏模式

    在 iPhone 应用里,有时我们想强行把显示模式从纵屏改为横屏(反之亦然),CocoaChina 会员 “alienblue” 为我们提供了两种思路 第一种:通过人为的办法改变view.transf ...

  5. [Java] SSH框架笔记_SSH三大框架的工作原理及流程

    Hibernate工作原理及为什么要用? 原理:1.通过Configuration().configure();读取并解析hibernate.cfg.xml配置文件2.由hibernate.cfg.x ...

  6. [Java] Eclipse+Spring学习(一)环境搭建

    转自:http://blog.sina.com.cn/s/blog_7c3736810100qhia.html 最近由于投了一家公司实习,他要java工程师,而我大学3年的精力都花到了ASP.NET和 ...

  7. Java基础知识强化之IO流笔记82:NIO之 Pipe(管道)

    1. Java NIO 管道是2个线程之间的单向数据连接.Pipe有一个source通道和一个sink通道.数据会被写到sink通道,从source通道读取. 这里是Pipe原理的图示: 2. Pip ...

  8. LRU算法实现

    JDK中的实现 在JDK中LinkedHashMap可以作为LRU算法以及插入顺序的实现,LinkedHashMap继承自HashMap,底层结合hash表和双向链表,元素的插入和查询等操作通过计算h ...

  9. VMware系统运维(十)部署虚拟化桌面 Horizon View 5.2 Connection Server安装

    部署桌面虚拟化,首先得安装连接服务器,下面我们开始安装Connection Server. 1.下载并安装以下软件,提示:只能在Win2008R2上安装,Win2012R2无法安装. 2.双击打开程序 ...

  10. React Native学习-measure测量view的宽高值

    measure()测量是根据view标签中的ref属性,使用方法如下: measureWatermarkerImage(){ this.refs.watermarkerImage.measure((a ...