大家对虚表并不陌生,都知道每个含有虚函数的类对象都有1个虚指针,但是在现实使用中,却总是因为这而调试半天,才发现原来是虚指针惹的祸。我这几天在调试代码时候也中招了,我的问题是这样的,如下图,CTree是最底层基类(非虚类), CSamplerTree(虚类)派生自CTree,CMSamplerTree,CASamplerTree派生自CSamplerTree,

                                                       

CTree中包括两个成员变量,QList <CTree *> childList;树中有多少个孩子节点;CTree *parent;当前树节点的父亲节点,程序中我大量使用CTree *pTree指针指向CSamplerTree、CMSamplerTree、CASamplerTree ,从而达到统一处理的目的,从而使代码很简洁,复用性高。但是谁曾想到,程序一运行就会崩溃,通过调试发现,CSamplerTree、CMSamplerTree、CASamplerTree的指针当指向CTree的指针时,地址均加了4,为什么呢?为了加深理解,我做了一个简单的测试代码:

  1. #include <stdio.h>class CBase {
  2. public:
  3. CBase() {}
  4. void func()
  5. {
  6. printf("base\n");
  7. }
  8. };
  9. class CDerived : public CBase {
  10. public:
  11. CDerived() {}
  12. virtual void func1()
  13. {
  14. printf("derived\n");
  15. }
  16. };
  17. void main()
  18. {
  19. CBase *pBase = new CDerived();
  20. pBase->func();
  21. CDerived *pDerived = (CDerived *)pBase;
  22. printf("%d %d\n", pDerived, pBase);
  23. pDerived->func();
  24. CBase *pBase1 = new CBase();
  25. pBase1->func();
  26. CDerived *pDerived1 = (CDerived *)pBase1;
  27. printf("%d %d\n", pDerived1, pBase1);
  28. pDerived1->func();
  29. }

下面是输出的结果,从结果可以看出派生类指针指向基类指针,指针地址会加4,基类指针指向派生类时,指针地址会减4。

base
200672 200676
derived
base
200740 200744
Press any key to continue

下面我们看看派生类对象和基类对象的内存是如何组织的,我们在上例的基础上引入2个变量,代码如下:

  1. #include <stdio.h>class CBase {
  2. public:
  3. CBase() {}
  4. void func()
  5. {
  6. printf("base\n");
  7. }
  8. int a;
  9. };
  10. class CDerived : public CBase {
  11. public:
  12. CDerived() {}
  13. virtual void func1()
  14. {
  15. printf("derived\n");
  16. }
  17. int b;
  18. };
  19. void main()
  20. {
  21. CBase *pBase = new CDerived();
  22. CDerived *pDerived = (CDerived *)pBase;
  23. printf("%d %d\n", pDerived, pBase);
  24. printf("%d %d %d\n", &pDerived->a, &pDerived->b, &pBase->a);
  25. }

200672 200676
200676 200680 200676
Press any key to continue
从输出结果我们可看出,CDerived对象的起始地址存放的是虚表指针vptr,接下来的是基类的成员变量,接下来再是自身的成员变量。

http://blog.csdn.net/rabinsong/article/details/8923137

--------------------------------------------------------

补充:一开始我没看懂这篇文章。后来才发现CBase没有虚函数,也就没有虚指针,所以它的实例头地址只能从自己的第一个成员变量开始。但是子类有,却又不能破坏原来的类结构,于是在它上面额外加了一个地址,用来保存虚指针!Oh, my god...

亲测,如果把CBase的func加上virtual,运行结果就变成了:

4398000 4398000 (两者地址值完全一致了)
4398004 4398008 4398004

派生类地址比基类地址少4(子类与基类指针强行转换的时候,值居然会发生变化,不知道Delphi BCB是不是也这样) good的更多相关文章

  1. C#中子类对基类方法的继承、重写和隐藏

    提起子类.基类和方法继承这些概念,肯定大家都非常熟悉.毕竟,作为一门支持OOP的语言,掌握子类.基类是学习C#的基础.不过,这些概念虽然简单,但是也有一些初学者可能会遇到的坑,我们一起看看吧.   子 ...

  2. Java 工具类 IpUtil - 获取本机所有 IP 地址,LocalHost 对应地址 IP

    Java 工具类 IpUtil - 获取本机所有 IP 地址,LocalHost 对应地址 IP IP 工具类 源代码: /** * <p> * * @author XiaoPengwei ...

  3. Effective Objective-C 2.0 — 第二条:类的头文件中尽量少引入其他头文件

    第二条:类的头文件中尽量少引入其他头文件 使用向前声明(forward declaring) @class EOCEmployer 1, 将引入头文件的实际尽量延后,只在确有需要时才引入,这样就可以减 ...

  4. UI(UGUI)框架(二)-------------UIManager单例模式与开发BasePanel面板基类/UIManage统一管理UI面板的实例化/开发字典扩展类

    UIManage单实例: /// 单例模式的核心 /// 1,定义一个静态的对象 在外界访问 在内部构造 /// 2,构造方法私有化 private static UIManager _instanc ...

  5. 在类的头文件里尽量少引入其它头文件 &lt;&lt;Effective Objective-C&gt;&gt;

    与C 和C++ 一样,Objective-C 也使用"头文件"(header file) 与"实现文件"(implementation file)来区隔代码.用 ...

  6. 介绍了如何取成员函数的地址以及调用该地址:C++

    摘要:介绍了如何取成员函数的地址以及调用该地址. 关键字:C++成员函数 this指针 调用约定 一.成员函数指针的用法 在C++中,成员函数的指针是个比较特殊的东西.对普通的函数指针来说,可以视为一 ...

  7. IP地址、子网掩码和地址分类

    http://blog.csdn.net/bluishglc/article/details/47909593?utm_source=tuicool&utm_medium=referral 实 ...

  8. Java常用类归纳(Object、System、Properties、包装类和工具类等等)

    Object类 Object 是类层次结构的根类.每个类都使用 Object 作为超类,所有对象(包括数组)都实现这个类的方法.了解Object的方法是很有必要的. protected Object ...

  9. 对象布局已知时 C++ 对象指针的转换时地址调整

    在我调试和研究 netscape 系浏览器插件开发时,注意到了这个问题.即,在对象布局已知(即对象之间具有继承关系)时,不同类型对象的指针进行转换(不管是隐式的从下向上转换,还是强制的从上到下转换)时 ...

随机推荐

  1. poj1160

    题目大意:在一个一维坐标轴上有v个(1<=v<=300)村庄,要建p(1<=p<=30)个邮局,每个村庄都到最近的邮局,要求最小的距离和.   四边形不等式,据说黑书上写得很高 ...

  2. Struts2+Ajax实现检测用户名是否唯一

    搞了慢慢两天,终于弄明白了怎么在Struts2框架中使用Ajax检测用户名的存在了.虽然,比起那些大牛们来,这速度确实够慢的,不过,最终弄出来还是满满的成就感啊. 闲话休提,言归正传.直接上代码: A ...

  3. Python进阶之闭包

    闭包 .note-content {font-family: "Helvetica Neue",Arial,"Hiragino Sans GB","S ...

  4. raphael 支持group(简)

    raphael 不支持group,里面有的set方法,只是把对象数组存起来,方法调用的时候,遍历都调用下,但是在实际需求上面感觉group还是瞒有用处的,可以控制group下面的节点的交互 比如地图区 ...

  5. python3--(变量)

    变量: Python 是动态类型语言, 也就是说不需要预先声明变量的类型.变量是对象的引用,变量只是将指针指向了对象所在的内存地址.变量的类型和值在赋值那一刻被初始化. 变量起名: 1.显式--> ...

  6. 查看linux版本的三种常用方法

    1) 登录到服务器执行 lsb_release -a ,即可列出所有版本信息,例如: [root@3.5.5Biz-46 ~]# lsb_release -a LSB Version: 1.3 Dis ...

  7. oracle正则表达式regexp_like的用法详解

    oracle正则表达式regexp_like的用法详解 /*ORACLE中的支持正则表达式的函数主要有下面四个:1,REGEXP_LIKE :与LIKE的功能相似2,REGEXP_INSTR :与IN ...

  8. 鼠标进入与离开的消息(覆盖CM_MOUSEENTER与CM_MOUSELEAVE消息)——Windows本身没有这样的消息

    unit Unit1; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, ...

  9. Java面试题之六

    三十一.如何取小数点前两位,并四舍五入. package com.lovo; public class TestDot2 { // 方法1 public void test1(double c) { ...

  10. C#DataTable DataSet DataRow区别详解

    DataSet 是C#中用来存储数据库数据的.其实,它的作用是在内存中模拟数据库.我们现实生活中的数据库从大到小的基本结构类似于:数据库实例,表,列,行.在C#语言中,我们在内存中也模拟出了一个这样的 ...