虚函数的工作原理——C++

C++规定了虚函数的行为,但将实现方法留给了编译器作者。不需要知道实现方法就可以使用虚函数,但了解需函数的工作原理有助于更好的理解概念,因此,这里对其进行介绍。
  通常,编译器处理虚函数的方法是:给每个对象添加一个隐藏的指针成员,该指针成员指向函数地址数组。这种数组称为虚函数表(virtual function table, vtbl)。虚函数表中存储了为对象进行声明的虚函数的地址。例如,基类对象包含一个指针,该指针指向基类中所有虚函数的地址表。派生类对象包含一个指向独立地址表的指针。如果派生类提供了虚函数的新定义,该函数表将保存新函数的地址;如果派生类没有重新定义虚函数,该vtbl将保存函数原始版本的地址。如果派生类定义了新的虚函数,则该函数也将被添加到vtbl中(参见下图)。注意,无论类中包含的虚函数是1个还是10个,都只需要在对象中添加1个地址成员,只是表的大小不同而已。
简而言之,使用虚函数时,在内存和执行速度方面有一定的成本,包括:
  1)每个对象都将增大,增大量为指向虚函数表的指针。
  2)对每个类,编译器都创建一个虚函数地址表(数组)。
  3)每个函数调用都需要执行一部额外的操作,即到表中查找地址。
虽然非虚函数的效率比虚函数稍高,但不具备动态联编(Dynimac Blinding)的功能。

例子:
#include <iostream>
using namespace std;
class a
{

private:
int aa;
public:
void set(int bb){aa=bb;}
int get(){return aa;}
a(int cc){aa=cc;}
virtual void display(){cout<<"a";}
};
class b:public a
{

public:
b(int ac):a(ac){}
//void display(){cout<<get();}
virtual void display(){cout<<"b";}
};

void main()
{
b *B=new b(2);
B->set(1);
B->display();
}

main()函数中
 
 b *B=new b(2)语句对应的汇编代码;
0041154D push 8        //注意 申请了8字节,一个字节保存成员变量,一个字节保存虚函数表指针  
0041154F call operator new (41120Dh)
00411554 add esp,4
00411557 mov dword ptr [ebp-0E0h],eax //上面8字节的空间地址保存在 [ebp-0E0h]中,栈桢中
.....
0041156F mov ecx,dword ptr [ebp-0E0h]  //上面8字节的空间地址放入ecx中,通过ecx传递给b类的构造函数
00411575 call b::b (411019h)


然后程序进入b类的构造函数 b(int ac):a(ac){}
 
0041168F pop ecx                      //上面8字节的空间地址  
00411690 mov dword ptr [ebp-8],ecx
00411693 mov eax,dword ptr [ac]
00411696 push eax                     //通过栈传递2这个参数  
00411697 mov ecx,dword ptr [this]     //还是通过ecx传递对象的8字节空间地址 
0041169A call a::a (41113Bh)         //调用a类的构造函数 

  然后程序进入a类的构造函数a(int cc){aa=cc;}
 
004116FF pop ecx
00411700 mov dword ptr [ebp-8],ecx    //还是通过ecx传递对象的8字节空间地址  
00411703 mov eax,dword ptr [this]     //还是通过ecx传递对象的8字节空间地址 
00411706 mov dword ptr [eax],offset a::`vftable' (41770Ch)  //把虚函数表的地址放入这个8字节空间的第                                                             //一个4字节空间 
0041170C mov eax,dword ptr [ebp-8]     //还是通过ecx传递对象的8字节空间地址 
0041170F mov ecx,dword ptr [ebp+8]    //上一函数通过栈传递过来的参数 2,放到ecx中 
00411712 mov dword ptr [eax+4],ecx   //把成员变量的值放入这个8字节空间的第  二个4字节空间                                                         
      -----------------------------------------------

        虚函数表地址

------------------------------------------------------

成员变量

---------------------------------------------------

 然后来到main函数的 B->display();
004115B2 mov eax,dword ptr [ebp-14h] //获得对象地址 
004115B5 mov edx,dword ptr [eax]     //把对象的虚函数表的地址放入edx中 
004115B7 mov esi,esp
004115B9 mov ecx,dword ptr [ebp-14h]  // 对象地址,来传递this指针
004115BC mov eax,dword ptr [edx]      //获得 虚函数表中函数的地址
004115BE call eax                     //调用函数  display
   

总结:C++中虚函数需要虚函数表支持,每个类中的虚函数都会各自保留一份执行程序。如果类中有虚函数或者继承的基类中有虚函数,就必须在所产生的对象中保存虚函数表地址,占据对象的第一个4字节空间。 



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值