返回信息流今晚研究了半天一个C++多继承与指针的问题,在这里分享一下。
这个标题,是因为看了码神@nuanyangyang之前的PHP的帖子,也求码神能来斧正一下。
下面直接贴代码
#include<iostream>
using namespace std;
#define DWORD unsigned long
class ClassA {
public: int a;
ClassA(){ a=1;
}
};
class ClassB {
public: int a;
ClassB(){ a=2; }
};
class ClassC : public ClassA,public ClassB {
public: int a;
ClassC(){ a=3; }
};
示例1:
int main()
{
DWORD* pObject=(DWORD*)(new ClassC());
ClassA* pA=(ClassA*)pObject;
ClassB* pB=(ClassB*)pObject;
ClassC* pC=(ClassC*)pObject;
cout<<pA<<endl<<pB<<endl<<pC<<endl;
cout<<pA->a<<pB->a<<pC->a<<endl;
return 0;
}
程序运行输出
0x8050438
0x8050438
0x8050438
113
看到这里,会觉得这不就是个继承与指针的问题么,简单!
pA,pB,pC指向同一块地址,输出时把指针转化成的对应类型,按此类型的格式计算偏移,输出对应地址的数据。
现在改变一下主程序;
示例2:
int main()
{
ClassC cObject;
ClassA* pA=(ClassA*)&cObject;
ClassB* pB=(ClassB*)&cObject;
ClassC* pC=(ClassC*)&cObject;
//cout<<pA<<endl<<pB<<endl<<pC<<endl;
cout<<pA->a<<pB->a<<pC->a<<endl;
return 0;
}
pA,pB,pC还相等么?看输出:
123
有没有一点惊喜
那看一下pA,pB,pC指向的实际地址吧,你猜一下敢不敢?
看结果:
0xbf69e13c //pA
0xbf69e140 //pB
0xbf69e13c //pC
结果是pA=pC,pB自动指向了对象Object起始地址后面4Byte(sizeof(A))的内存块.肿么会这么智能?
示例2跟示例1的区别到底在哪里?程序是如何识别的?
唯一的区别就是赋给pA,pB,pC的,一个是动态分配的内存,一个是对象地址。
对于示例1还好理解,动态分配的内存没有任何信息,只能通过指针去找到他,改变指针类型就改变了读取这块内存的方式(不同类型各个字段的长度不同),
示例2呢,通过 ClassC cObject创建的对象,编译器会识别出来并自动计算父类指针对应的区域地址。
如果我把&cObject转换成非父类类型呢?他怎么找对应的地址?答案是编译器已经考虑到这个情况并加以限制。
如下代码:
class ClassS
{
public:
char a;
ClassS(){ a='s';}
};
int main()
{
ClassC cObject;
ClassA* pA=(ClassA *)&cObject;
ClassB* pB=(ClassB *)&cObject;
ClassC* pC=&cObject;
ClassS pS=&cObject; //编译错误
cout<<pA<<endl<<pB<<endl<<pC<<pS<<endl;
cout<<pA->a<<pB->a<<pC->a<<pS->a<<endl;
//return 0;
return 0;
}
输出:
In function 'int main()':
Line 30: error: conversion from 'ClassC*' to non-scalar type 'ClassS' requested
compilation terminated due to -Wfatal-errors.
到这里这个问题基本上已经研究的差不多清楚了。
更进一步,C++这么多继承关系,编译器如何由子类自动找到父类指针对应的地址的呢?脑子糊涂了,想不到了
写个帖子,今晚算是没白费吧,不准确的地方还请大牛指正。
这是一条镜像帖。来源:北邮人论坛 / cpp / #74300同步于 2013/10/7
该镜像源已超过 30 天没有更新,可能在源站已被删除。
CPP机器人发帖
惊喜
fwh19890125
2013/10/7镜像同步10 回复
订阅后,新回复会通过你的通知中心匿名送达。
9 条回复
&cObject是可以通过强制转换成非父类类型指针的。
将cObjec地址强制转换为ClassS *: ClassS* pS=(ClassS *)&cObject;
这个时候再输出就没有问题了 : cout<<pA->a<<pB->a<<pC->a<<pS->a<<endl;
这个时候pS->a访问的实际上是ClassA类的int a 转换成char的值
【 在 kuhu 的大作中提到: 】
: &cObject是可以通过强制转换成非父类类型指针的。
: 将cObjec地址强制转换为ClassS *: ClassS* pS=(ClassS *)&cObject;
: 这个时候再输出就没有问题了 : cout<<pA->a<<pB->a<<pC->a<<pS->a<<endl;
: ...................
额 这里确实疏忽了
不过结合上下文 我本意就是想定义指针pS而不是对象。确实可以强制类型转换,但却得不到想要的结果。
改正的代码如下
class ClassS
{
public:
char a;
ClassS(){ a='s';}
};
int main()
{
ClassC cObject;
ClassA* pA=(ClassA *)&cObject;
ClassB* pB=(ClassB *)&cObject;
ClassC* pC=&cObject;
ClassS* pS=(ClassS*)&cObject; //编译没错,帖子中疏忽写错了
cout<<pA<<endl<<pB<<endl<<pC<<endl<<pS<<endl;
cout<<hex<<pA->a<<pB->a<<pC->a<<pS->a<<endl;
//return 0;
return 0;
}
输出如下:
0xbfacfd68
0xbfacfd6c
0xbfacfd68
0xbfacfd68
123
对C++不太了解,但是C++标准有对“Object Layout”进行规定吗?
所谓Object Layout,指的是给定一个对象的定义,得到它的每个成员变量在它所占据的内存中的位置,以及它占据的内存中的其它元数据的位置。
其实这个都谈不上对象内存布局了~毕竟没有涉及虚表~
基本上就可以看作栈增生了~
【 在 nuanyangyang 的大作中提到: 】
: 对C++不太了解,但是C++标准有对“Object Layout”进行规定吗?
: 所谓Object Layout,指的是给定一个对象的定义,得到它的每个成员变量在它所占据的内存中的位置,以及它占据的内存中的其它元数据的位置。
-------------------------------------------------------------------
顺路插磕:
ClassC pObject;
ClassA* pA=(ClassA*)&pObject;
ClassB* pB=(ClassB*)&pObject;
ClassC* pC=&pObject;
cout<<pA->a<<pB->a<<pC->a<<endl;
// 等效于
cout<<*((int*)(&pC->a) - 2)<<*((int*)(&pC->a) - 1)<<pC->a<<endl;
或者你可以构造一个内存组织形式近似于ClassX的这样的结构体:
struct LikeA {
int a;
};
struct LikeB {
int a;
};
struct LikeC {
struct LikeA _A;
struct LikeB _B;
int a;
};
struct LikeC pstruct;
pstruct._A.a = 1;
pstruct._B.a = 2;
pstruct.a = 3;
struct LikeA* pa = (struct LikeA*)&pstruct;
struct LikeB* pb = (struct LikeB*)&pstruct;
struct LikeC* pc = &pstruct;
cout<<pa->a<<pb->a<<pc->a<<endl;
这样反而会比Class更好~因为Class的指针会因为你向DWORD*或者void*转化而丢失内存组织特性,即会出现你主楼里第一种情况~但struct的指针只要你能确信你的内存结构是哪种,那么不管指针怎么来回转换指针类型,指针都永远固定在一个位置(类似于主楼的第一种情况,指针相同,结果113)(除非你自己去移动它~我个人认为这点上struct反而比class处理得好,至少更方便做底层的字节运算处理~虽然有人会认为这不够智能~)
【 在 fwh19890125 的大作中提到: 】
: 今晚研究了半天一个C++多继承与指针的问题,在这里分享一下。
: 这个标题,是因为看了码神@nuanyangyang之前的PHP的帖子,也求码神能来斧正一下。
: 下面直接贴代码
: ...................
【 在 fwh19890125 的大作中提到: 】
: 今晚研究了半天一个C++多继承与指针的问题,在这里分享一下。
: 这个标题,是因为看了码神@nuanyangyang之前的PHP的帖子,也求码神能来斧正一下。
: 下面直接贴代码
: ...................
很有钻研精神啊,想起来我以前琢磨过的一题,有点类似,lz有兴趣做做?
class InterfaceA{
public:
virtual void Foo(double *) = 0;
};
class InterfaceB{
public:
virtual void Foo(int *) = 0;
};
class CProblem : public InterfaceA, public InterfaceB{
public:
void Foo(double *){cout << "double, ";}
void Foo(int *){cout << "int, ";}
};
int main(){
CProblem *theProblem = new CProblem();
void *voidPointer = theProblem;
((InterfaceB*)(InterfaceA*)theProblem)->Foo(NULL);
((InterfaceB*)voidPointer)->Foo(NULL);
((InterfaceB*)(CProblem *)voidPointer)->Foo(NULL);
delete theProblem;
return 0;
} //RESULT??????
RESULT??不是重点,重点是WHY?
【 在 fentoyal 的大作中提到: 】
: 很有钻研精神啊,想起来我以前琢磨过的一题,有点类似,lz有兴趣做做?
: class InterfaceA{
: public:
: ...................
首先谢谢你的问题,让我更清楚的认识到这类问题的关键所在。
你的问题里三个输出不同(跟我的问题一样)关键在于指针的类型转换,当子类指针转换为父类指针时编译器认为是可以转换的,并且计算出对应的父类地址,比如说theProblem转换成InterfaceA*时.
但是类类型转换目标不是其父类指针时,编译器认为是不和谐的,就进行“强制”类型转换,这样的话其实是先将指针转换成(void *),然后转换成对应类型指针,这样就丢失了结构信息,是不会自动计算偏移的。因此((InterfaceA*)theProblem)再次转换成InterfaceB*时进行强制类型转换,地址不会偏移,调用的是A的函数。
这个弄清楚后,对于voidProblem的转换就容易多了。
不知跟你琢磨后的结论是否一致? 不过我倒是还有两个问题,
1是是否只有子类转父类时才会自动寻址?比如父类转子类呢?
2是帖子里那个问题,父类转子类这种指针的自动寻址是如何实现的?