c++ 反汇编 虚函数

自作多情 提交于 2020-01-13 20:53:29

  虚函数是面向对象程序设计的关键组成部分。对于具有虚函数的类而言,构造函数和析构函数的识别流程更加简单。而且,在类中定义了虚函数之后,如果没有提供默认的构造函数,编译器必须提供默认的构造函数。
  对象的多态性需要通过虚表和虚表指针来完成,虚表指针被定义在对象首地址的前4字节处,因此虚函数必须作为成员函数使用。由于非成员函数没有this指针,因此无法获得虚表指针,进而无法获取虚表,也就无法访问虚函数。

class CVirtual
{
public:
    ~CVirtual()
    {
        printf("~CVirtual");
    }
    CVirtual()
    {
    }
    CVirtual(int nNumber)
    {
        m_nNumber = nNumber;
    }
    virtual int GetNumber()
    {
        return m_nNumber;
    }
    virtual void SetNumber(int nNumber)
    {
        m_nNumber = nNumber;
    }
private:
    int m_nNumber;
};

类中只定义一个int类型的数据成员,类大小却为8。当类中含有虚函数时,对象空间中第一项存放虚表指针。

35:     // 获取含有虚函数表的类大小
    36:     int nSize = sizeof(CVirtual);
0007739D C7 45 EC 08 00 00 00 mov         dword ptr [nSize],8  

虚表在构造函数中设置

13:     CVirtual()
00077170 55                   push        ebp  
00077171 8B EC                mov         ebp,esp  
00077173 81 EC CC 00 00 00    sub         esp,0CCh  
00077179 53                   push        ebx  
0007717A 56                   push        esi  
0007717B 57                   push        edi  
0007717C 51                   push        ecx  
0007717D 8D BD 34 FF FF FF    lea         edi,[ebp-0CCh]  
00077183 B9 33 00 00 00       mov         ecx,33h  
00077188 B8 CC CC CC CC       mov         eax,0CCCCCCCCh  
0007718D F3 AB                rep stos    dword ptr es:[edi]  
0007718F 59                   pop         ecx  
00077190 89 4D F8             mov         dword ptr [this],ecx  
00077193 8B 45 F8             mov         eax,dword ptr [this]  //取this指针
00077196 C7 00 54 2E 11 00    mov         dword ptr [eax],offset CVirtual::`vftable' (0112E54h)//设置对象第一项为虚表。  
    14:     {
    15:     }
0007719C 8B 45 F8             mov         eax,dword ptr [this]  
0007719F 5F                   pop         edi  
000771A0 5E                   pop         esi  
000771A1 5B                   pop         ebx  
000771A2 8B E5                mov         esp,ebp  
000771A4 5D                   pop         ebp  
000771A5 C3                   ret  

析构时也要还原自身虚表

 9:     ~CVirtual()
    10:     {
000771C0 55                   push        ebp  
000771C1 8B EC                mov         ebp,esp  
000771C3 81 EC CC 00 00 00    sub         esp,0CCh  
000771C9 53                   push        ebx  
000771CA 56                   push        esi  
000771CB 57                   push        edi  
000771CC 51                   push        ecx  
000771CD 8D BD 34 FF FF FF    lea         edi,[ebp-0CCh]  
000771D3 B9 33 00 00 00       mov         ecx,33h  
000771D8 B8 CC CC CC CC       mov         eax,0CCCCCCCCh  
000771DD F3 AB                rep stos    dword ptr es:[edi]  
000771DF 59                   pop         ecx  
000771E0 89 4D F8             mov         dword ptr [this],ecx  
000771E3 8B 45 F8             mov         eax,dword ptr [this]  
000771E6 C7 00 54 2E 11 00    mov         dword ptr [eax],offset CVirtual::`vftable' (0112E54h)  
    11:         printf("~CVirtual");
000771EC 68 60 2E 11 00       push        offset string "~CVirtual" (0112E60h)  
000771F1 E8 CF A1 FF FF       call        _printf (0713C5h)  
000771F6 83 C4 04             add         esp,4  
    12:     }
000771F9 5F                   pop         edi  
000771FA 5E                   pop         esi  
000771FB 5B                   pop         ebx  
000771FC 81 C4 CC 00 00 00    add         esp,0CCh  
    12:     }
00077202 3B EC                cmp         ebp,esp  
00077204 E8 92 BC FF FF       call        __RTC_CheckEsp (072E9Bh)  
00077209 8B E5                mov         esp,ebp  
0007720B 5D                   pop         ebp  
0007720C C3                   ret  

使用对象直接调用自身虚函数,不会间接访问虚表调用。

40:     MyVirtual.SetNumber(argc);
002673C1 8B 45 08             mov         eax,dword ptr [argc]  
002673C4 50                   push        eax  
002673C5 8D 4D DC             lea         ecx,[MyVirtual]  
002673C8 E8 23 A2 FF FF       call        CVirtual::SetNumber (02615F0h)  
41:     printf("%d\r\n", MyVirtual.GetNumber());
002673CD 8D 4D DC             lea         ecx,[MyVirtual]  
002673D0 E8 FF B3 FF FF       call        CVirtual::GetNumber (02627D4h)  
002673D5 50                   push        eax  
002673D6 68 6C 2E 30 00       push        offset string "%d\r\n" (0302E6Ch)  
002673DB E8 E5 9F FF FF       call        _printf (02613C5h)  
002673E0 83 C4 08             add         esp,8 

当使用对象指针,引用时需要使用虚表。

    43:     p = &MyVirtual;
001773E3 8D 45 DC             lea         eax,[MyVirtual]  
001773E6 89 45 D0             mov         dword ptr [p],eax  
    44:     p->SetNumber(66);
001773E9 8B F4                mov         esi,esp  
001773EB 6A 42                push        42h  
001773ED 8B 45 D0             mov         eax,dword ptr [p]  //eax 存对象地址
001773F0 8B 10                mov         edx,dword ptr [eax]//EDX 存虚表指针  
001773F2 8B 4D D0             mov         ecx,dword ptr [p]  
001773F5 8B 42 04             mov         eax,dword ptr [edx+4] //取虚表第二项,即SetNumber函数 
    44:     p->SetNumber(66);
001773F8 FF D0                call        eax  
001773FA 3B F4                cmp         esi,esp  
001773FC E8 9A BA FF FF       call        __RTC_CheckEsp (0172E9Bh)  
    45:      printf("%d\r\n", p->GetNumber());
00177401 8B 45 D0             mov         eax,dword ptr [p]  
00177404 8B 10                mov         edx,dword ptr [eax] //edx存虚表指针 
00177406 8B F4                mov         esi,esp  
00177408 8B 4D D0             mov         ecx,dword ptr [p]  
0017740B 8B 02                mov         eax,dword ptr [edx] //虚表第一项 
0017740D FF D0                call        eax  
0017740F 3B F4                cmp         esi,esp  
00177411 E8 85 BA FF FF       call        __RTC_CheckEsp (0172E9Bh)  
00177416 50                   push        eax  
00177417 68 6C 2E 21 00       push        offset string "%d\r\n" (0212E6Ch)  
0017741C E8 A4 9F FF FF       call        _printf (01713C5h)  
00177421 83 C4 08             add         esp,8 

 

(学习类的继承与多态后,还要学习更多相关内容。)Orz

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!