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

news/2024/7/7 12:47:06
本文作者: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()的调用歧义.
 



http://www.niftyadmin.cn/n/3654631.html

相关文章

js对象的构造函数与原型

从__proto__属性说起 我们都知道,javascript创建对象一般有两种方式: 方式一:通过字面量创建,例如: var obj {} //创建一个空对象 var obj1 { name :js} //创建带属性的对象方式二:通过new关键字,如&am…

小品: 关于C++引用

此文缘起我看了这篇文章:http://blog.csdn.net/newslxw/archive/2006/08/08/1038972.aspx 还记得当年读书时, 牛牛的C老师告诉我: "引用, 其实就是别名, 与其所引用的对象完全等价". 可是, 我还是有这点疑问: "等价的含义, 是指的什么? 难道是说在最底层的实现…

搞懂Js中的this,call和apply方法

为了更好的看懂这篇文章,建议先要理解js对象的原型和原型链相关的知识,若不了解,可以看上一篇文章《js对象的构造函数与原型》 this的理解 在全局中使用this,this为window对象谁调用了对象中属性,this就是谁this.xxx…

Lua入门系列----pil学习笔记之Getting Start

本文作者:sodme本文出处:http://blog.csdn.net/sodme声明: 本文可以不经作者同意, 任意复制, 转载, 但任何对本文的引用都请保留文章开始前三行的作者, 出处以及声明信息. 谢谢.很多高深的人物都说过类似的话:"语言层面的东西, 都是表面的东西, 最核心的是设计模式, 是算法…

js中hasOwnProperty和instanceof的使用

hasOwnProperty hasOwnProperty方法是用来判断某个属性是否是属于自身对象的,该方法仅用于判断自身对象,不会查找原型链。 案例如下: Person.prototype.name 原型 Person.prototype.myFc function(){}function Person(){this.age 18this.…

Lua入门系列----pil学习笔记之 Type and Values (1)

本文作者:sodme本文出处:http://blog.csdn.net/sodme声明: 本文可以不经作者同意, 任意复制, 转载, 但任何对本文的引用都请保留文章开始前三行的作者, 出处以及声明信息. 谢谢.参考资源:http://www.lua.org/pil/ 一 个小小的脚本语言都能玩出这么多花样来&#xff…

js函数中的形参,实参和arguments对象

形参:形式参数,是函数作用域内的变量。 实参:调用函数时,传入的参数,有对应的形参时,实参会赋值给形参。 案例: var MyF function(a){} //a为形参MyF(1) //1为实参在javascript中的函数中&…

对标号地址的另一种相对寻址方式

汇编程序中, 对数据访问时, 通常是这样的:_asm{...DATA_LABLE:_emit 0x87_emit 0xa0_emit 0x49_emit 0x90...mov ebx, dword ptr [DATA_LABLE]...}其中, 当程序编译之后, mov指令中的DATA_LABLE标号地址会被转成一个绝对地址. 而有时绝对地址这一点可能会对这样一种需求带来障碍…