这篇文章我要简单地讲解下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-19-成员变量修饰符

    /** 成员变量修饰符 1.@public:(公开)只要导入头文件,任何位置都可以直接访问. 2.@protected:(半公开)可以在本类和子类当中进行访问.(默认) 3.@private:(私有) ...

  2. 在C语言中,double、long、unsigned、int、char类型数据所占字节数

    和机器字长及编译器有关系: 所以,int,long int,short int的宽度都可能随编译器而异.但有几条铁定的原则(ANSI/ISO制订的): 1 sizeof(short int)<= ...

  3. hash_map map

    什么时候需要用hash_map,什么时候需要用map? 总体来说,hash_map 查找速度会比map快,而且查找速度基本和数据数据量大小,属于常数级别;而map的查找速度是log(n)级别.并不一定 ...

  4. C语言中的各种修饰符

    C允许同时使用多于一个的修饰符,这就使得可以创建一下各种类型: int board[8][8];//int数组的数组 int **ptr;//指向int的指针的指针 int *risk[10];//具 ...

  5. windows下使用代理连接github

    在公司的windows下使用github,因为github被墙,查了查,发现给在windows下的git制定代理很简单,一句话   git config --global http.proxy htt ...

  6. php调试工具总结

    一:XDebug+Webgrind 二:XHProf

  7. Java再学习——栈(stack)和堆(heap)

    一.内存分配的策略 按照编译原理的观点,程序运行时的内存分配有三种策略,分别是静态的,栈式的,和堆式的. 静态存储分配是指在编译时就能确定每个数据目标在运行时刻的存储空间需求,因而在编译时就可以给他们 ...

  8. WWH学习模式

    WWH是"What+Why+How"的简称,是对学习方法最完美的概括."如果不按照WWH这种模式来教学,90%的结果是老师没教好,学生学不好." 1.What( ...

  9. 每天一道面试题(2):实现strncpy

    目录 0. 为何要写strncpy? 1. 源码及测试结果 2. 面试注意事项 3. 小结

  10. [Windows] 解决kmplayer播放rmvb文件音视不同步

    1. 到該帖子 http://www.kmplayer.com/forums/showthread.php?t=8755 下載附件中的4個檔案放到KMP 根 目錄下(此文件已在附件中上传) 2.於KM ...