关闭

C++多重继承下的指针类型转换

发表于:2013-4-12 09:50

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:SurpassLi    来源:51Testing软件测试网采编

  在C++中,指针的类型转换是经常发生的事情,比如将派生类指针转换为基类指针,将基类指针转换为派生类指针。指针的本质其实就是一个整数,用以记录进程虚拟内存空间中的地址编号,而指针的类型决定了编译器对其指向的内存空间的解释方式。

  基于上面的理解,我们似乎可以得出一个结论,C++中对指针进行类型转换,不会改变指针的值,只会改变指针的类型(即改变编译器对该指针指向内存的解释方式),但是这个结论在C++多重继承下是 不成立的。

  看下面一段代码:

#include <iostream>
 using namespace std;
 
 class CBaseA
 {
 public:
     char m_A[32];
 };
 
 class CBaseB
 {
 public:
     char m_B[64];
 };
 
 class CDerive : public CBaseA, public CBaseB
 {
 public:
     char m_D[128];
 };
 
 
 int main()
 {
     auto pD = new CDerive;
     auto pA = (CBaseA *)pD;
     auto pB = (CBaseB *)pD;
 
     cout << pA << '\n' << pB << '\n' << pD << endl;
     cout << (pD == pB) << endl;
 }

  这段代码的输出是:

  0x9f1080
  0x9f10a0
  0x9f1080
  1

  可以看出,指向同一个堆上new出来的内存指针,在经过类型转换之后,其值会发生改变。究其原因,要从C++中多重继承的内存布局说起。

  new CDerive;执行之后,生成的内存布局如下:

  同时我们注意到,pB与pD的指针差值正好是CBaseA占用的内存大小32字节,而pA与pD都指向了同一段地址。这是因为,将一个派生类的指针转换成某一个基类指针,编译器会将指针的值偏移到该基类在对象内存中的起始位置。

  可是为什么C++要这样设计呢?

  试想,沿用上面的场景,如果pB和pA都指向对象的首地址,那么使用pB指针来定位CBaseB的成员变量m_B时,编译器应该将pB指针偏移32个字节,从而跳过CBaseA的内存部分。而pB指针如果是这样产生的auto pB = new CBaseB;,那么使用pB指针来定位CBaseB的成员变量m_B时,偏移量应该为0。

  关键在于对于一个指针而言,编译器不关心也无法知道该指针的来源(一种极端情况,指针是从其他模块传递过来的),而只是把它视为一个有指针类型的整数。所以对于CBaseB类型的指针,取CBaseB的成员变量m_B时,偏移量应该通通为0,这是通过CBaseB的类声明就可以统一决策的事情。

  说到这里,pD和pB的指针地址为什么不一样大家应该清楚了,可是为什么下面的代码输出是1呢?

  cout << (pD == pB) << endl;

  输出1表示pD和pB是相等的,而刚刚我们才说明了,pD和pB的地址是相差了32个字节的。

  其实这也是编译器为大家屏蔽了这种指针的差异,当编译器发现一个指向派生类的指针和指向其某个基类的指针进行==运算时,会自动将指针做隐式类型提升已屏蔽多重继承带来的指针差异。因为两个指针做比较,目的通常是判断两个指针是否指向了同一个内存对象实例,在上面的场景中,pD和pB虽然指针值不等,但是他们确确实实都指向了同一个内存对象(即new CDerive;产生的内存对象 ),所以编译器又在此处插了一脚,让我们可以安享==运算的上层语义。

《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号