内存布局: 虚继承 ---Empty virtual base classs (空虚基类)

2024-04-28 02:32

本文主要是介绍内存布局: 虚继承 ---Empty virtual base classs (空虚基类),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

上一次只是给出一个代码 http://blog.csdn.net/OpenHero/archive/2006/04/02/648005.aspx,和运行过程中的内存位置,现在具体的按照步骤分析一下C++中虚拟继承中的情况,
  由于C++ stand(C++ 标准委员会) 并没有规定具体实现的办法,各个编译器厂商可能都采用不同的实现方法,
  或许在不同的年代使用的方法也不相同,有可能就会得到不同的结果,这里,我采用VC7.1作为调试工具,由潜
  入深的一步步分析C++ 虚继承中的内存布局;
  这一部分由Empty virtual base calss (空虚基类:空的calss)入手,查看VC7.1中是如何实现虚拟继承的.
  下面给出测试的代码:
  一,菱形的继承模式
  class A
  { };
  class B : virtual public A
  {
  };
  class C : virtual public A
  {
  };
  class D : public C, public B
  {
  };
  int _tmain(int argc, _TCHAR* argv[])
  {
  A a;
  B b;
  C c;
  D d;
  int *pd = (int*)&d;
  int *pd_c = (int*)(C*)(&d);
  int *pd_b = (int*)(B*)(&d);
  int *pd_a = (int*)(A*)(&d);
  cout<< sizeof(a) << endl;
  cout<< sizeof(b) << endl;
  cout<< sizeof(c) << endl;
  cout<< sizeof(d) << endl;
  cout<< pd << endl;
  cout<< pd_c << endl;
  cout<< pd_b << endl;
  cout<< pd_a << endl;
  return 0;
  }
  有人会问,
  class A
  { };
  A a, 其中的a会有大小嘛?当然,a里面什么都没有,姑且就认为它是空的吧,但是,编译器如何才能记住a,而又
  是空?你能想到一种实现的方法嘛?所以,编译器为了记住空的a,在a的内部放入了一个char的标记,作为它的
  唯一标识,这样编译器才能认识那个a. 如果你有兴趣,你可以尝试定义A a,b; 然后你看看a和b是否是一
  样的?现阶段来说,应该是不一样的,或许以后谁会发明新的实现方式,那个时候或许是一样也有可能了。
  现在让我们看看上面程序的输出情况,如下:
  1
  4
  4
  8
  0012FEAC
  0012FEAC
  0012FEB0
  0012FEB4
  这里我先不对上面的输出做出解释,或许你看了这样的输出会有疑惑,是不是和你想象的不一样?带着疑问
  继续往下面看。
  下面我再给出两段代码,同时给出他们各自的输出,然后再对这些输出做出解释:
  二,六边形(菱形一)
  class A
  { };
  class B : virtual public A
  {
  };
  class C : virtual public A
  {
  };
  class D : /*virtual */public B
  {
  };
  class E : /*virtual */public C
  {
  };
  class F : public E, public D
  {
  };
  int _tmain(int argc, _TCHAR* argv[])
  {
  A a;
  B b;
  C c;
  D d;
  E e;
  F f;
  int *pf = (int*)&f;
  int *pf_e = (int*)(E*)(&f);
  int *pf_d = (int*)(D*)(&f);
  int *pf_c = (int*)(C*)(&f);
  int *pf_b = (int*)(B*)(&f);
  int *pf_a = (int*)(A*)(&f);
  cout<< sizeof(a) << endl;
  cout<< sizeof(b) << endl;
  cout<< sizeof(c) << endl;
  cout<< sizeof(d) << endl;
  cout<< sizeof(f) << endl;
  cout<< pf << endl;
  cout<< pf_e << endl;
  cout<< pf_d << endl;
  cout<< pf_c << endl;
  cout<< pf_b << endl;
  cout<< pf_a << endl;
  return 0;
  }
  输出结果:
  1
  4
  4
  4
  4
  8
  0012FE94
  0012FE94
  0012FE98
  0012FE94
  0012FE98
  0012FE9C
  三,六边形(菱形二)
  class A
  { };
  class B : virtual public A
  {
  };
  class C : virtual public A
  {
  };
  class D : virtual public B
  {
  };
  class E : virtual public C
  {
  };
  class F : public E, public D
  {
  };
  int _tmain(int argc, _TCHAR* argv[])
  {
  A a;
  B b;
  C c;
  D d;
  E e;
  F f;
  int *pf = (int*)&f;
  int *pf_e = (int*)(E*)(&f);
  int *pf_d = (int*)(D*)(&f);
  int *pf_c = (int*)(C*)(&f);
  int *pf_b = (int*)(B*)(&f);
  int *pf_a = (int*)(A*)(&f);
  cout<< sizeof(a) << endl;
  cout<< sizeof(b) << endl;
  cout<< sizeof(c) << endl;
  cout<< sizeof(d) << endl;
  cout<< sizeof(e) << endl;
  cout<< sizeof(f) << endl;
  cout<< pf << endl;
  cout<< pf_e << endl;
  cout<< pf_d << endl;
  cout<< pf_c << endl;
  cout<< pf_b << endl;
  cout<< pf_a << endl;
  return 0;
  }
  输出结果:
  1
  4
  4
  8
  8
  16
  0012FE84
  0012FE84
  0012FE88
  0012FE8C
  0012FE90
  0012FE8C
  下面对其中的一,二做分析,三是一个有趣的现象,你可以考虑考虑:)
  可以看到,只要有virtual的地方,都多出4个字节,用来干嘛?
  在VC的编译器中,采用了virtual base calss pointer的方式来实现虚继承,从而保证子类回溯到基类的时
  候只存在一个实体。多出来的4个字节就是用来存放各自的指向virtual base class 的地址的。
  你可能又要问,a的大小为1,b,c……的大小只是4个4个增加,考虑内存对其,那不是会更大吗?
  看到前面提到的一个问题了吗?
  编译器只是为了记住a的时候才把a标示出来,如果知道a是空的,它既然有了子类,子类表示出来的时候,
  当然基类就存在其中了:)
  你可能又问? 那为什么不把子类也表示为1,不就行了吗?当然不行,就像上面提到的,因为是为了实现虚
  继承的菱形一样的继承样式,在子类中只存在基类一个实体,所以,就才用了指针的方式,而不是把整个实
  体放到子类的内存中。

这篇关于内存布局: 虚继承 ---Empty virtual base classs (空虚基类)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/942115

相关文章

怎样通过分析GC日志来定位Java进程的内存问题

《怎样通过分析GC日志来定位Java进程的内存问题》:本文主要介绍怎样通过分析GC日志来定位Java进程的内存问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、GC 日志基础配置1. 启用详细 GC 日志2. 不同收集器的日志格式二、关键指标与分析维度1.

Java内存分配与JVM参数详解(推荐)

《Java内存分配与JVM参数详解(推荐)》本文详解JVM内存结构与参数调整,涵盖堆分代、元空间、GC选择及优化策略,帮助开发者提升性能、避免内存泄漏,本文给大家介绍Java内存分配与JVM参数详解,... 目录引言JVM内存结构JVM参数概述堆内存分配年轻代与老年代调整堆内存大小调整年轻代与老年代比例元空

C++11委托构造函数和继承构造函数的实现

《C++11委托构造函数和继承构造函数的实现》C++引入了委托构造函数和继承构造函数这两个重要的特性,本文主要介绍了C++11委托构造函数和继承构造函数的实现,具有一定的参考价值,感兴趣的可以了解一下... 目录引言一、委托构造函数1.1 委托构造函数的定义与作用1.2 委托构造函数的语法1.3 委托构造函

Java 继承和多态的作用及好处

《Java继承和多态的作用及好处》文章讲解Java继承与多态的概念、语法及应用,继承通过extends复用父类成员,减少冗余;多态实现方法重写与向上转型,提升灵活性与代码复用性,动态绑定降低圈复杂度... 目录1. 继承1.1 什么是继承1.2 继承的作用和好处1.3 继承的语法1.4 子类访问父类里面的成

Java继承映射的三种使用方法示例

《Java继承映射的三种使用方法示例》继承在Java中扮演着重要的角色,它允许我们创建一个类(子类),该类继承另一个类(父类)的所有属性和方法,:本文主要介绍Java继承映射的三种使用方法示例,需... 目录前言一、单表继承(Single Table Inheritance)1-1、原理1-2、使用方法1-

C++高效内存池实现减少动态分配开销的解决方案

《C++高效内存池实现减少动态分配开销的解决方案》C++动态内存分配存在系统调用开销、碎片化和锁竞争等性能问题,内存池通过预分配、分块管理和缓存复用解决这些问题,下面就来了解一下... 目录一、C++内存分配的性能挑战二、内存池技术的核心原理三、主流内存池实现:TCMalloc与Jemalloc1. TCM

Redis过期删除机制与内存淘汰策略的解析指南

《Redis过期删除机制与内存淘汰策略的解析指南》在使用Redis构建缓存系统时,很多开发者只设置了EXPIRE但却忽略了背后Redis的过期删除机制与内存淘汰策略,下面小编就来和大家详细介绍一下... 目录1、简述2、Redis http://www.chinasem.cn的过期删除策略(Key Expir

C#继承之里氏替换原则分析

《C#继承之里氏替换原则分析》:本文主要介绍C#继承之里氏替换原则,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录C#里氏替换原则一.概念二.语法表现三.类型检查与转换总结C#里氏替换原则一.概念里氏替换原则是面向对象设计的基本原则之一:核心思想:所有引py

Java内存区域与内存溢出异常的详细探讨

《Java内存区域与内存溢出异常的详细探讨》:本文主要介绍Java内存区域与内存溢出异常的相关资料,分析异常原因并提供解决策略,如参数调整、代码优化等,帮助开发者排查内存问题,需要的朋友可以参考下... 目录一、引言二、Java 运行时数据区域(一)程序计数器(二)Java 虚拟机栈(三)本地方法栈(四)J

CSS3 布局样式及其应用举例

《CSS3布局样式及其应用举例》CSS3的布局特性为前端开发者提供了无限可能,无论是Flexbox的一维布局还是Grid的二维布局,它们都能够帮助开发者以更清晰、简洁的方式实现复杂的网页布局,本文给... 目录深入探讨 css3 布局样式及其应用引言一、CSS布局的历史与发展1.1 早期布局的局限性1.2