多态的内幕--(C++, C)语言两个版本

多态的内幕--(C++, C)语言两个版本

    本文通过分析C++编译器生成的汇编代码,分析多态的机制。并实现了一个C语言版本。

    在编译性语言里面,多态真的是一个伟大的发明。它可以现在写好代码,编译好,并且可以调用未来的代码。这多少有了点动态的感觉。

很多人,也在脚本语言里面抱怨,为什么不提供多态的功能啊。脚本语言里面,一个函数参数,可以传递任何类型,甚至可以通过函数名的字符串调用函数,

这样多态的作用就小了很多。对于面向对象来说,最重要的两个概念莫过于 继承 和 多态。继承可以减少代码重复,多态可以减少大量的条件判断,if else switch

如果在代码中太多,你的程序应该不怎么面向对象。

    废话不说了,先给一个用于分析的程序:

 

ExpandedBlockStart.gif 代码
#include  < iostream >
using   namespace  std;

class  Base
{
public :
    
virtual   void  vfun1() {cout  <<   "  Base::vfun1() "   << endl;}
    
virtual   void  vfun2() {cout  <<   "  Base::vfun2() "   << endl;}
    
virtual   void  vfun3() {cout  <<   "  Base::vfun3() "   << endl;}
};

class  Concrete: public  Base
{
public :
    
void  vfun1() {cout  <<   "  Concrete::vfun1() "   << endl;}
    
void  vfun2() {cout  <<   "  Concrete::vfun2() "   << endl;}
};

void  override_demo2(Base  & obj)
{
    obj.vfun1();
    obj.vfun2();
    obj.vfun3();
}

typedef 
long  point_t;   // 32 位系统 和 64 位系统上 都表示标准指针的长度,但是可能不兼容16位系统,在编译的时候修改一下
typedef  void  ( * func)();

inline 
void   *   getvfptr( void   * p,  int  offset)
{
    point_t 
* =  (point_t  * ) * (point_t  * )p;
    
//  cout << q[0] << endl
    
//       << q[1] << endl
    
//       << q[2] << endl;
     return  ( void   * )(q[offset]);
}

void  override_demo(Base  & obj)
{
    func f;
    f 
=  (func)getvfptr( & obj,  0 );
    f();
    f 
=  (func)getvfptr( & obj,  1 );
    f();
    f 
=  (func)getvfptr( & obj,  2 );
    f();
}

int  main()
{
    Base base_obj;
    Concrete concrete_obj;
    cout 
<<   " override_demo: "   <<  endl;
    override_demo(base_obj);
    override_demo(concrete_obj);
    cout 
<<   " override_demo2: "   <<  endl;
    override_demo2(base_obj);
    override_demo2(concrete_obj);
}

 

这基本上是一个最简单的多态的演示了。我们先来看看 override_demo 这个函数。这个函数没有使用系统使用的多态功能,但是也实现了多态。

通过仔细分析可以发现,这个代码的原理是取出 Base 类的地址,如果,Base 定义了 虚函数,那么会在Base的头部自动插入一个指针,指向虚表数组。

函数调用是通过函数的地址,编译器会在Base类里面插入这个虚表,里面填上按照顺序填上虚函数的地址,在子类中,会复制一份Base的虚表数组,

如果函数被重新定义,那么替换这个虚表中的函数地址,否则就用Base 类里面的地址,在调用虚函数的地方,把obj.vfunc1() 改成 调用虚表中的第一个函数。

这样,即时子类指针转换成了父类指针,但是子类地址指针指向的虚表还是子类的,所以,会调用子类虚表中的第一个函数。

上面的解释太抽象,可以看看汇编的代码:

这是 override_demo2 的汇编代码,很能说明问题:

void override_demo2(Base &obj)
{
00411950  push        ebp 
00411951  mov         ebp,esp
00411953  sub         esp,0C0h
00411959  push        ebx 
0041195A  push        esi 
0041195B  push        edi 
0041195C  lea         edi,[ebp-0C0h]
00411962  mov         ecx,30h
00411967  mov         eax,0CCCCCCCCh
0041196C  rep stos    dword ptr es:[edi]
    obj.fun1();
0041196E  mov         eax,dword ptr [obj] //取出obj的地址,就是getvfptr 中p的值
00411971  mov         edx,dword ptr [eax] //取出obj第一个元素的值,也就是 getvfptr 中的 *(ponit_t *)p , 取出指针所指向的地址
00411973  mov         esi,esp
00411975  mov         ecx,dword ptr [obj]
00411978  mov         eax,dword ptr [edx] //取出obj第一个元素的指针,指向的第一个元素,也就是 getvfptr 中的 q[0]
0041197A  call        eax   //调用函数
0041197C  cmp         esi,esp
0041197E  call        @ILT+470(__RTC_CheckEsp) (4111DBh)
    obj.fun2();
00411983  mov         eax,dword ptr [obj]
00411986  mov         edx,dword ptr [eax]
00411988  mov         esi,esp
0041198A  mov         ecx,dword ptr [obj]
0041198D  mov         eax,dword ptr [edx+4] //第二个函数
00411990  call        eax 
00411992  cmp         esi,esp
00411994  call        @ILT+470(__RTC_CheckEsp) (4111DBh)
    obj.fun3();
00411999  mov         eax,dword ptr [obj]
0041199C  mov         edx,dword ptr [eax]
0041199E  mov         esi,esp
004119A0  mov         ecx,dword ptr [obj]
004119A3  mov         eax,dword ptr [edx+8] //第三个函数
004119A6  call        eax 
004119A8  cmp         esi,esp
004119AA  call        @ILT+470(__RTC_CheckEsp) (4111DBh)
}

这样看来,调用虚函数的代码并不是很高,但是可以发现,虚函数是不可能内联的,因为,调用它必须通过地址。而且,在之类中必须声明为 virtual

否则,这个函数不会放入虚表中,也就不能产生多态了。

依照这个思路,你可以改造成一个C语言的多态的方法。比如你定义一个基结构,它是一个函数指针列表,然后,定义几个子结构,子结构是和基结构一样排序

的函数指针列表。下面是一个例子:

ExpandedBlockStart.gif 代码
#include  < stdio.h >
#include 
< stdlib.h >

typedef 
void  func();
struct  Base
{
    func 
* vfun1;
    func 
* vfun2;
    func 
* vfun3;
};

struct  Child
{
    func 
* vfun1;
    func 
* vfun2;
    func 
* vfun3;
    
char   * hello;
};


void  base_vfunc1()
{
    printf(
"  base_vfunc1\n " );
}

void  base_vfunc2()
{
    printf(
"  base_vfunc2\n " );
}

void  base_vfunc3()
{
    printf(
"  base_vfunc3\n " );
}

struct  Base *  init_base()
{
    
static   struct  Base base_vtable;
    base_vtable.vfun1 
=  base_vfunc1;
    base_vtable.vfun2 
=  base_vfunc2;
    base_vtable.vfun3 
=  base_vfunc3;
    
return   & base_vtable;
}

void  child_vfunc3()
{
    printf(
"  child_vfunc3\n " );
}
struct  Child  *  init_child()
{
    
struct  Child  * child;
    
struct  Base   * base_vtable;
    child 
=  malloc( sizeof ( struct  Child));
    base_vtable 
=  init_base();
    child
-> vfun1  =  base_vtable -> vfun1;
    child
-> vfun2  =  base_vtable -> vfun2;
    child
-> vfun3  =  child_vfunc3;
    child
-> hello  =   " hello world " ;
    
return  child;
}

free_child(
struct  Child  * ch)
{
    
if  (ch) free(ch);
    ch 
=  NULL;
}

void  override_demo( void   * p)
{
    
struct  Base  * base ;
    
base   =  ( struct  Base  * )p;
    
base -> vfun1();
    
base -> vfun2();
    
base -> vfun3();
}

int  main()
{
    
struct  Child  * ch   =  init_child();
    
struct  Base  * base   =  init_base();
    printf(
" base\n " );
    override_demo(
base );
    printf(
" child\n " );
    override_demo(ch);
    printf(ch
-> hello);
    free_child(ch);
}

 

这样,你写一个函数,可以调用不同的代码了。

当然,可能没有面向对象这样直观了。

 

posted @ 2011-01-20 14:01 暮夏 阅读( ...) 评论( ...) 编辑 收藏
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值