首页
社区
课程
招聘
[旧帖] [原创]virtual函数调用的代价 0.00雪花
发表于: 2010-1-21 23:10 2603

[旧帖] [原创]virtual函数调用的代价 0.00雪花

2010-1-21 23:10
2603

定义一个类。

view plaincopy to clipboardprint?
class CA   
{   
public:   
    void FunA()   
    {   
    };   
};  
class CA
{
public:
        void FunA()
        {
        };
};

通过指针调用FunA。

view plaincopy to clipboardprint?
CA* pa = new CA;   
pa->FunA();  
        CA* pa = new CA;
        pa->FunA();

此时,转入汇编,我们看看pa->FunA()到底是怎么做的。

pa->FunA();

// 汇编展开如下:

004113E7  mov         ecx,dword ptr [pa]

// 函数调用。
004113EA  call        CA::FunA (411005h)
// 看看FunA做了什么。中间做了一个跳转

00411005  jmp         CA::FunA (411420h)

// 继续展开。

void FunA()
{

// EBP压栈,为函数调用保护现场。
00411420  push        ebp  
00411421  mov         ebp,esp
00411423  sub         esp,0CCh
00411429  push        ebx  
0041142A  push        esi  
0041142B  push        edi  
0041142C  push        ecx  

//  函数FunA并没有任何操作,为什么汇编展开成这么多,我暂时也不知道为什么。
0041142D  lea         edi,[ebp-0CCh]
00411433  mov         ecx,33h
00411438  mov         eax,0CCCCCCCCh
0041143D  rep stos    dword ptr es:[edi]
0041143F  pop         ecx  
00411440  mov         dword ptr [ebp-8],ecx
};

// 函数返回
00411443  pop         edi  
00411444  pop         esi  
00411445  pop         ebx  
00411446  mov         esp,ebp
00411448  pop         ebp  
00411449  ret            

至此。函数调用完成。

可是如果我们为FunA声明为virtual,会发生什么呢?

view plaincopy to clipboardprint?
class CA   
{   
public:   
    virtual void FunA()   
    {   
    };   
};  
class CA
{
public:
        virtual void FunA()
        {
        };
};

// 看看汇编吧。

pa->FunA();

// 调用咱开。
0041146D  mov         eax,dword ptr [pa]
00411470  mov         edx,dword ptr [eax]
00411472  mov         esi,esp
00411474  mov         ecx,dword ptr [pa]

// 将虚函数的地址,从虚表中取出来,放入到EAX
00411477  mov         eax,dword ptr [edx]

// 调用虚函数。下面这一句才是真铮的调用FunA。

// 前面的调用均是准备工作,

// 目的是将FunA在CA对象的虚表指针指向的虚表中的地址取出来。
00411479  call        eax  

// 安全性检查,由于是Debug下运行,如下汇编会在release会优化
0041147B  cmp         esi,esp
0041147D  call        @ILT+355(__RTC_CheckEsp) (411168h)

对00411479  call        eax  这一句汇编展开。

CA::FunA:
00411005  jmp         CA::FunA (411510h)

virtual void FunA()
{
00411510  push        ebp  
00411511  mov         ebp,esp
00411513  sub         esp,0CCh
00411519  push        ebx  
0041151A  push        esi  
0041151B  push        edi  
0041151C  push        ecx  
0041151D  lea         edi,[ebp-0CCh]
00411523  mov         ecx,33h
00411528  mov         eax,0CCCCCCCCh
0041152D  rep stos    dword ptr es:[edi]
0041152F  pop         ecx  
00411530  mov         dword ptr [ebp-8],ecx
};
00411533  pop         edi  
00411534  pop         esi  
00411535  pop         ebx  
00411536  mov         esp,ebp
00411538  pop         ebp  
00411539  ret         

后面的进入到函数FunA内部,与非virutual的函数调用汇编一致。

上述汇编均在Debug模式下展开。

可以发现,使用virtual的主要效率损失是在函数调用的准备阶段,

我们需要在对象的虚表指针的某一个位置取出函数实际地址,继而转入函数地址进行函数调用。

在Release模式下,我们看看函数调用的准备阶段到底发生了什么。

不使用virtual:

在本机上FunA的调用直接被优化掉了。断点无法命中。

于是我觉得需要给他加一些有意义的代码,我们只看函数调用准备阶段到底做了什么。

pa->FunA();

// 展开时就是一个call指令,直接指向函数地址。
0040100A  call        dword ptr [__imp__rand (4020A4h)]

使用virtual:

pa->FunA();
00401024  mov         edx,dword ptr [eax]
00401026  mov         ecx,eax
00401028  mov         eax,dword ptr [edx]
0040102A  call        eax  

// 对call调用展开,发现该函数就是一个ret指令。因为该函数并没有任何有意义的执行语句。

virtual void FunA()
{
};
00401000  ret              
--- No source file -------------------------------------------------------------
00401001  int         3   
00401002  int         3   
00401003  int         3   
00401004  int         3   
00401005  int         3   
00401006  int         3   
00401007  int         3   
00401008  int         3   
00401009  int         3   
0040100A  int         3   
0040100B  int         3   
0040100C  int         3   
0040100D  int         3   
0040100E  int         3   
0040100F  int         3   
--- e:\workspace\virtualdemo\virtualdemo\virtualdemo.cpp -----------------------
};

结论:

不使用virtual,一个成员函数的调用只需要一条call指令。

如果使用virtual,一个成员函数调用,需要额外的三条mov指令来

呵呵,当我们在享用virtual带来的多态时,

也带来了一些效率上的损失。

同时virtual也会抑制inline以及调用优化。

对于不必要的virtual还是不要为好。

个人意见,欢迎拍砖。


[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

收藏
免费 7
支持
分享
最新回复 (8)
雪    币: 9
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
求邀请码呀,这个文章是从我自己的blog里转的,前几天深入学习了下virtual函数就写了这个
2010-1-21 23:12
0
雪    币: 139
活跃值: (25)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
3
哈哈,鼓励楼主。貌似邀请码不是这么容易获取的。
2010-1-22 11:21
0
雪    币: 724
活跃值: (81)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
4
这个有点简单,楼主有精力不妨研究一下虚继承,然后结合虚函数再写一篇,相信写全了能获得邀请码(版主不给你也别找我)。
2010-1-22 17:50
0
雪    币: 72
活跃值: (10)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
5
额,虚函数和虚表你写了,有人要看虚继承?哈…我下午放学发上来…现在手机…
2010-1-26 12:59
0
雪    币: 64
活跃值: (97)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
路过的顶一下!!
2010-1-26 14:27
0
雪    币: 30
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
这个我能看懂....
2010-1-26 19:48
0
雪    币: 7
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
谢谢楼主分享~~~顶下
2010-1-26 22:34
0
雪    币: 72
活跃值: (10)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
9
下午没发上来,现在补充。

源代码

class A
{
public:
   int x;
};

class B1 : virtual public A  //虚继承
{
public:
   int b1;
};

class B2 : virtual public A
{
public:
   int b2;
};

class C : public B1 , public B2
{
public:
   int c;
};

void main()
{
   C com;
   com.c = 9;
   com.b1 = 8;
   com.b2 = 6;
   com.x = 0x100;
}

触摸板发的,不方便,就不打字了。
http://hi.baidu.com/y32asm/blog/item/93cb074ba20f3a2808f7efc6.html

shaoweixiang稍微详细点的,在这个地方了。
卖弄了,大牛飘过...
2010-1-26 23:25
0
游客
登录 | 注册 方可回帖
返回
//