分享
 
 
 

孔乙己之五----虚函数(下)

王朝other·作者佚名  2006-08-09
窄屏简体版  字體: |||超大  

本文作者:sodme

本文出处:http://blog.csdn.net/sodme

声明: 本文可以不经作者同意, 任意复制, 转载, 但任何对本文的引用都请保留文章开始前三行的作者, 出处以及声明信息. 谢谢.

本文所需代码可从以下地址获得( 此地址含有多继承c++和asm代码 ):

http://sodme.dev.googlepages.com/kyj_05_code.txt

前文中, 我们知道了单继承时的虚函数处理方法, 那么, 多继承时, 又是如何处理的呢? 我们仍然重点考察以下几个方面:

1. 虚函数表的变化;

2. 构造函数的变化;

3. 虚函数调用代码的变化.

三个类的继承关系如图:

|----> Base1Class

MyClass |

|----> Base2Class

类MyClass的虚函数表:

.long 0

.long _ZTI7MyClass

.long _ZN7MyClass14virtual_test_1Ev

.long _ZN10Base1Class14virtual_test_3Ev

.long _ZN7MyClass15virtual_test_myEv

.long -8

.long _ZTI7MyClass

.long _ZN10Base2Class14virtual_test_2Ev

.long _ZN10Base2Class14virtual_test_3Ev

咦? 从形式上看, 与单继承时确实有所不同, 整个虚函数表似乎被分成了两个部分: 第一部分是Base1Class的, 第二部分是Base2Class的. 那个中幺机何在呢? 别急, 慢慢看.

类MyClass的构造函数:

movl 8(%ebp), %eax

movl %eax, (%esp)

call _ZN10Base1ClassC2Ev ;调用Base1Class的构造函数

movl 8(%ebp), %eax

addl $8, %eax

movl %eax, (%esp)

call _ZN10Base2ClassC2Ev ;调用Base2Class的构造函数, 注意其传递的this指针

movl $_ZTV7MyClass+8, %edx

movl 8(%ebp), %eax

movl %edx, (%eax) ;将MyClass的虚函数表地址放在this处

movl $_ZTV7MyClass+28, %edx

movl 8(%ebp), %eax

movl %edx, 8(%eax) ;将MyClass虚函数表中属于Base2Class的那部分虚函数放在了this+8处

movl 8(%ebp), %eax

movl $1, 16(%eax) ;对数据data1的访问是: this+16

movl 8(%ebp), %eax

movl $2, 20(%eax) ;对数据data2的访问是: this+20

通过以上的语句和注释, 我们可以发现:

类MyClass的构造函数中, 分别调用了Base1Class和Base2Class的构造函数, 这并不奇怪, 但奇怪的是传递给Base2Class构造函数的this指针变成了MyClass::this+8. 另外, 类MyClass的虚函数表初始时, 分别初始化了两个地方, 一处是this, 一处是this+8. 而类MyClass的两个数据成员data1 和 data2的访问, 也不再是前文单继承情况下的 this+12 和 this+16, 而是多了4个字节. 种种迹象表明, 多继承情况下, 对象结构似乎变成了这样:

| |

|--------------------------------|

this -> | Base1Class虚函数表地址 | 0

|--------------------------------|

| Base1Class::base_1_data | +4

|--------------------------------|

| Base2Class虚函数表地址 |+8

|--------------------------------|

| Base2Class::base_2_data | +12

|--------------------------------|

| MyClass::data1 | +16

|--------------------------------|

| MyClass::data2 | +20

|--------------------------------|

| |

下面, 我们再看调用方式的变化.

pMyClass->virtual_test_1():

movl -16(%ebp), %eax ;取this指针

movl (%eax), %eax ;取虚函数表地址

movl (%eax), %edx ;取virtual_test_1()函数地址

movl -16(%ebp), %eax

movl %eax, (%esp)

call *%edx ;调用virtual_test_1()

pMyClass->virtual_test_2():

movl -16(%ebp), %eax ;取this指针

movl 8(%eax), %eax ;this = this + 8

movl (%eax), %edx ;取MyClass中属于Base2Class的虚函数表地址,即virtual_test_2()首址

movl -16(%ebp), %eax ;取this指针

addl $8, %eax ;this = this + 8

movl %eax, (%esp)

call *%edx ;调用virtual_test_2()

pMyClass->virtual_test_my():

movl -16(%ebp), %eax ;取this指针

movl (%eax), %eax ;取虚函数表地址

addl $8, %eax ;取virtual_test_my()存放的地址

movl (%eax), %edx ;取virtual_test_my()函数首址

movl -16(%ebp), %eax

movl %eax, (%esp)

call *%edx ;调用virtual_test_my()

由此, 可以看出, 在多继承情况下, 在对象结构模型中, 会分开多处存放多个虚函数表的不同起始地址(当然, 虚函数表仍然只有一份, 只是在各处存放的针对于这同一个虚函数表的起始地址不同而已). 那么, 为什么这样作呢?

换个角度想一下, 自然也就明白了. 类MyClass分别继承于两个互不相干的类: Base1Class 和 Base2Class. 由于Base1Class和Base2Class互相没有继承关系, 那么, Base1Class的虚函数表中就不会有Base2Class的虚函数信息, 更重要的, 他们的类成员数据不会被另一个类包含. 反之亦然. 这样, 也就导致同时继承于这二者的类MyClass无法通过唯一的一个this指针来访问分属于两个类的不同的数据, 所以, 把它们分开管理几乎是必然的.

但是, 形如以下的语句:

pMyClass = new MyClass;

Base2Class * pBase2Class;

pBase2Class = pMyClass;

pBase2Class->virtual_test_2();

如果pBase2Class的值仍然是pMyClass的this指针, 那么pBase2Class->virtual_test_2()这样的调用, 岂不是有问题了吗? 因为pBase2Class是Base2Class类型, 按Base2Class的类定义, 它的虚函数表是:

.long 0

.long _ZTI10Base2Class

.long _ZN10Base2Class14virtual_test_2Ev

.long _ZN10Base2Class14virtual_test_3Ev

那么, pBase2Class->virtual_test_2()将会被转化成以下形式:

movl -12(%ebp), %eax ;取this指针

movl (%eax), %eax ;取Base2Class虚表地址

movl (%eax), %edx ;取virtual_test_2()地址

movl -12(%ebp), %eax

movl %eax, (%esp)

call *%edx

但是, 我们知道, pMyClass的虚表明明是:

.long 0

.long _ZTI7MyClass

.long _ZN7MyClass14virtual_test_1Ev

.long _ZN10Base1Class14virtual_test_3Ev

.long _ZN7MyClass15virtual_test_myEv

.long -8

.long _ZTI7MyClass

.long _ZN10Base2Class14virtual_test_2Ev

.long _ZN10Base2Class14virtual_test_3Ev

从此虚表中可以看到, virtual_test_2()地址, 应该是+28呀?!

呵呵. 一切玄妙皆在这条赋值语句"pBase2Class = pMyClass;", 这条语句, 偷偷干了这些事:

movl -16(%ebp), %eax ; -16(%ebp) 是 pMyClass

addl $8, %eax ; eax = pMyClass->this + 8

movl %eax, -32(%ebp)

jmp .L31

.L29:

movl $0, -32(%ebp)

.L31:

movl -32(%ebp), %eax

movl %eax, -12(%ebp) ;将 this+8 存入了 pBase2Class 变量中

this+8! 又是this+8! 没错. 当我们执行 pBase2Class = pMyClass 这条向下兼容的赋值语句时, 编译器会检查他们的继承派生关系, 并将正确的this指针赋给pBase2Class, 而并不是把this指针直接赋值左边的变量. 有人说, C++难, 可能也就是难在这些不容易为人听知的地方吧. 隐藏的东西越多, 学习的开销越大.

在我的测试代码中, 大家可以发现, 我注释掉了一条语句:

//pMyClass->virtual_test_3();

之所以把它注释掉, 是为了说明这样一个问题:

1. Base1Class和Base2Class可以拥有同名的虚函数, 无引用他们的情况下, 可以编译通过;

2. 但是, 如果有对同名虚函数的引用, 编译器则会报"未决的或容易引起歧义的调用"之类的错误.

但是, 如果换一种方式调用, 则是可以过关的:

pMyClass = new MyClass;

pBase2Class = pMyClass;

pBase2Class->virtual_test_3();

原因很显然, pBase2Class本身的类型, 已经消除了virtual_test_3()的调用歧义.

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有