关于多重继承和虚函数的问题。

Colo 2001-09-14 04:08:04
关于多重继承和虚函数的问题。

今日在写程序时遇到一个问题百思不得其解.源代码如下:

class CA
{
public:
virtual void GetAPoint(void** p) =0;
virtual void FunctionA()=0;
};

class CB
{
public:
virtual void GetBPoint(void** p) =0;
virtual void FunctionB()=0;
};

class CC:public CA,public CB
{
public:
void GetAPoint(void** p);
virtual void GetBPoint(void** p);
virtual void FunctionB();
virtual void FunctionA();
};

void CC::FunctionA()
{
AfxMessageBox("Function A");
}

void CC::FunctionB()
{
AfxMessageBox("Function B");
}

void CC::GetBPoint(void **p)
{
*p=(CB*)this;
}

void CC::GetAPoint(void **p)
{
*p=(CA*)this;
}

//问题出现在下面
CC c;
CA* p;
c.GetBPoint ((void**)&p);
p->FunctionA(); //什么它实际是进入到FunctionB()函数中?我调用的是FunctionA函数呀.
...全文
990 45 打赏 收藏 转发到动态 举报
写回复
用AI写文章
45 条回复
切换为时间正序
请发表友善的回复…
发表回复
fancy_kevin 2001-09-19
  • 打赏
  • 举报
回复
不好意思,[2]是不确切的,这同编译器有关,涉及到编译器如何对虚表布局,有的在[0]位置放置虚表大小。
针对VC编译器。
p->functionA();
有如下代码:

00402167 mov ecx,dword ptr [ebp-18h]
0040216A mov edx,dword ptr [ecx]
0040216C mov esi,esp
0040216E mov ecx,dword ptr [ebp-18h]
00402171 call dword ptr [edx+4]
这里ecx是p的地址,edx是虚表的地址,[edx+4]取的是虚表中偏移为4,即[1]的函数
ylredsun 2001-09-18
  • 打赏
  • 举报
回复
gz
yyc_csdn 2001-09-18
  • 打赏
  • 举报
回复
当将c的地址指针强制转化位其几类CB的指针后,p指向的当然是CB类的实例,类在调用虚函数时,编译系统将做如下转换:
p->functionA();

p->(vtblptr[index])();
index为虚函数的索引号。
你的那两个虚函数索引号一致当然会出现这种情况拉
blue_teeth 2001-09-18
  • 打赏
  • 举报
回复
IUnkown的其余三个接口都到哪里去了??
blue_teeth 2001-09-18
  • 打赏
  • 举报
回复
我看到最后却被搞糊涂了。
(*p._vptr[2])(p);
为什么是 [2] ? 而不是[4]或[1]

到底CC在内存中是怎么排列的???
programlover 2001-09-17
  • 打赏
  • 举报
回复
打扰各位了!
求救:
我在VC++的工程中加入Microsoft ADO Data Control, version 6.0 (OLEDB)后,
再发布到客户机,程序就不能运行了!


c_whfhf 2001-09-17
  • 打赏
  • 举报
回复
CC:GETBPOINT()只是做了赋值,而P还是指向A的指针,调用虚函数时还是A的VPTR
Colo 2001-09-17
  • 打赏
  • 举报
回复
to Tningfeng(宁丰) 
什么咚咚?
Tningfeng 2001-09-17
  • 打赏
  • 举报
回复
¶àÕ§¿´Êé
Colo 2001-09-17
  • 打赏
  • 举报
回复
咦?看来分数给早了.
fancy_kevin 2001-09-17
  • 打赏
  • 举报
回复
对于p->FunctionA(); 实际上是(*p._vptr[2])(p);
由于c.GetBPoint ((void**)&p);中p实际上是Cb的地址,所以调用Cb虚表中相应的函数,又由于FunctionA同FunctionB的参数相同,所以成立。
inside1 2001-09-17
  • 打赏
  • 举报
回复
to: caigzhi(caigzhi)
对于您所描述的在内存中c对象的内存结构,我认为是不对的,成员函数是不会占用对象的内存的,对于普通成员函数,在编译时会把所有函数调用处转为函数地址直接调用,对于虚函数,会把函数地址保存在虚函数表中,运行时通过虚函数表调用,所以对于c对象的内存结构中只有两个虚函数表的地址。另外,在32位系统中地址长度是4字节,而不是2字节。
caigzhi 2001-09-17
  • 打赏
  • 举报
回复
在内存中c对象的内存结构如下
0x0012ff28 &c
0x0012ff28 CA
0x0012ff28 CA::GetApoint()
0x0012ff2A CA::FunctionA()
0x0012ff2C CB
0x0012ff2C CB::GetBpoint()
0x0012ff2E CB::FunctionB()
一条函数为双字节存放,所以地址总是差2.

以下为执行代码的汇编码:
56: CC c;
004011D8 lea ecx,[ebp-8]
004011DB call @ILT+5(CC::CC) (0040100a)
57: CA* p;
58: c.GetBPoint ((void**)&p);
004011E0 lea eax,[ebp-0Ch]
004011E3 push eax
004011E4 lea ecx,[ebp-4]
004011E7 call @ILT+20(CC::GetBPoint) (00401019)
59: p->FunctionA(); //什么它实际是进入到FunctionB()函数中?我调用的是FunctionA函数呀.
004011EC mov ecx,dword ptr [ebp-0Ch]
004011EF mov edx,dword ptr [ecx]
004011F1 mov esi,esp
004011F3 mov ecx,dword ptr [ebp-0Ch]
004011F6 call dword ptr [edx+4]
004011F9 cmp esi,esp
004011FB call __chkesp (00401330)

不难看出在
c.GetBPoint ((void**)&p);
执行后p将指向CB,值为0x0012ff2C;

此时他的形式还是CA *,
编译器把p->FunctionA()翻译成调用对象地址即p的值偏移2的调用,
现在p的值偏移到CB,所以是对CB的偏移2的调用,即FunctonB()。

Colo 2001-09-17
  • 打赏
  • 举报
回复
to lixiner(大汤姆狼) 
to ripper(rIPPER) 
to caigzhi(caigzhi) 
你们三个人的解释非常经典.简直就是step by step.多谢了.
inside1 2001-09-17
  • 打赏
  • 举报
回复
CC c在内存中占8个字节,只能有2个指针,内容如下:
CA::vptr 4字节
CB::vptr 4字节

看下列语句:
CC c;
void * p;
p = (CA*)&c;
p = (CB*)&c;
如果分别打印前后p的值,会发现其值是不一样的,分别是CA::vptr,CB::vptr。(同一地址赋值给同一指针变量,结果会不一样,很奇怪吧)
c.GetBPoint ((void**)&p);实际得到了c对象的第二个虚函数表的指针;
p->FunctionA();因为p定义成指向CA的指针,所以编译器在编译时会把这条语句编译成调用p所指向虚函数表的第一个函数,即(p[0])();当你经过一番巧妙的转变,骗过编译器把CB虚函数表的指针赋值给p后,程序按((p)[0])()执行,就会运行CB虚函数表的第一个函数既FunctionB()。
ripper 2001-09-17
  • 打赏
  • 举报
回复
更正
2. 再执行p->FunctionA()就根据CA::vptr+某个offset找到FunctionA的地址,进行调用
ripper 2001-09-17
  • 打赏
  • 举报
回复
真巧,前两天刚刚看到一篇讲这个问题的文章,来自cuj ,Jim Hyslop 和 Herb Sutter的专栏,文章的标题是Conversations: Roots. 建议去查阅一下。

在这里试着简单说明一下这个问题,也算是加强一下对上面文章的理解 ;)

class CC在内存里面是这个样子的

CC::vptr

CA::vptr

CB::vptr


你的程序中
c是CC实例
p是指向CA类型的

c.GetBPoint((void**)&p)调用执行的是:
1. CC类指针this强制转换到CB类指针,就是说把指向CB::vptr首地址赋值给了p
2. 再执行p->FunctionA()就根据CB::vptr+某个offset找到FunctionA的地址,进行调用

问题是类型是CA的指针p指向了CB类型,调用FunctionA时加上一个offset恰好是CB::vptr+offset

3. 那么得到的地址是FunctionB的地址,这样实际调用到的就是FunctionB


呵呵,看了前面虾们的意见发现和lixiner(大汤姆狼)说的一样;)

没有想如何解决这个问题,好像比较麻烦,因为不同的类搀和在一起了 ;)

不过个人认为随意的使用cast尤其是强制转换(reinterpret_cast)是很不好的,在多继承里用更容易引起麻烦。Conversations: Roots里说的是类似的个问

题,不过那个更加隐蔽一点。

Congy 2001-09-16
  • 打赏
  • 举报
回复
建议使用static_cast
*p=static_cast< CB* >(this);
beni 2001-09-15
  • 打赏
  • 举报
回复
多重继承我也常用,没问题

它的程序是比较怪异
建议你把

class INondelegatingUnknown
{
public:
virtual HRESULT __stdcall NondelegationQueryInterface(const IID& iid, void **ppv) = 0 ;
virtual ULONG __stdcall NondelegatingAddRef() = 0;
virtual ULONG __stdcall NondelegationRelease() = 0;
};
中的后两个函数互换一下位置试试,看它还正常吗?

linghushaonian 2001-09-14
  • 打赏
  • 举报
回复
很忙,gz
加载更多回复(24)

16,473

社区成员

发帖
与我相关
我的任务
社区描述
VC/MFC相关问题讨论
社区管理员
  • 基础类社区
  • Web++
  • encoderlee
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

        VC/MFC社区版块或许是CSDN最“古老”的版块了,记忆之中,与CSDN的年龄几乎差不多。随着时间的推移,MFC技术渐渐的偏离了开发主流,若干年之后的今天,当我们面对着微软的这个经典之笔,内心充满着敬意,那些曾经的记忆,可以说代表着二十年前曾经的辉煌……
        向经典致敬,或许是老一代程序员内心里面难以释怀的感受。互联网大行其道的今天,我们期待着MFC技术能够恢复其曾经的辉煌,或许这个期待会永远成为一种“梦想”,或许一切皆有可能……
        我们希望这个版块可以很好的适配Web时代,期待更好的互联网技术能够使得MFC技术框架得以重现活力,……

试试用AI创作助手写篇文章吧