C++ 底层分析 4.虚函数表

1.对象和指针调用虚函数

class Base			
{			
public:			
    void Function_1()			
    {			
        printf("Function_1...\n");			
    }			
    virtual void Function_2()			
    {			
        printf("Function_2...\n");			
    }			
};		
void TestMethod()				
{				
	Base base;			
				
	base.Function_1();			
	00401090 8D 4D FC             lea         ecx,[ebp-4]			
	00401093 E8 9F FF FF FF       call        @ILT+50(Base::Function_1) (00401037)			
				
	base.Function_2();			
	00401098 8D 4D FC             lea         ecx,[ebp-4]			
	0040109B E8 65 FF FF FF       call        @ILT+0(Base::Function_2) (00401005)			
				
	Base* pb = &base;			
				
	pb->Function_1();			
	004010A6 8B 4D F8             mov         ecx,dword ptr [ebp-8]			
	004010A9 E8 89 FF FF FF       call        @ILT+50(Base::Function_1) (00401037)			
				
	pb->Function_2();			
	004010AE 8B 4D F8             mov         ecx,dword ptr [ebp-8]			
	004010B1 8B 11                mov         edx,dword ptr [ecx]			
	004010B3 8B F4                mov         esi,esp			
	004010B5 8B 4D F8             mov         ecx,dword ptr [ebp-8]			
	004010B8 FF 12                call        dword ptr [edx]			
}				
				

总结:
1. 通过对象调用时,virtual函数与普通函数都是E8 Call
2. 通过指针调用时,virtual函数是FF Call,也就是间接Call

2.虚函数表

class Base				
{				
public:				
    int x;				
    int y;				
    virtual void Function_1()				
    {				
        printf("Function_1...\n");				
    }				
    virtual void Function_2()				
    {				
        printf("Function_2...\n");				
    }				
};				
pb->Function_1();							
0040D9E3 8B 4D F0             mov         ecx,dword ptr [ebp-10h]							
0040D9E6 8B 11                mov         edx,dword ptr [ecx]							
0040D9E8 8B F4                mov         esi,esp							
0040D9EA 8B 4D F0             mov         ecx,dword ptr [ebp-10h]							
0040D9ED FF 12                call        dword ptr [edx]							
							
							
pb->Function_2();							
0040D9F6 8B 45 F0             mov         eax,dword ptr [ebp-10h]							
0040D9F9 8B 10                mov         edx,dword ptr [eax]							
0040D9FB 8B F4                mov         esi,esp							
0040D9FD 8B 4D F0             mov         ecx,dword ptr [ebp-10h]							
0040DA00 FF 52 04             call        dword ptr [edx+4]							

总结:

  1. 当类中有虚函数时,会多一个属性,4个字节
  2. 多出的属性是一个地址,指向一张表,里面存储了所有虚函数的地址

3.验证虚函数表

class Base					
{					
public:					
	int x;				
	int y;				
    virtual void Function_1()					
    {					
        printf("Function_1...\n");					
    }					
    virtual void Function_2()					
    {					
        printf("Function_2...\n");					
    }					
    virtual void Function_3()					
    {					
        printf("Function_3...\n");					
    }					
};					
					
					
					
void TestMethod()					
{					
	//查看 Sub 的虚函数表				
    Base base;					
					
	//对象的前四个字节就是虚函数表				
	printf("base 的虚函数表地址为:%x\n",*(int*)&base);				
					
	//通过函数指针调用函数,验证正确性				
    typedef void(*pFunction)(void);					
					
	pFunction pFn;				
					
	for(int i=0;i<3;i++)				
	{				
		int temp = *((int*)(*(int*)&base)+i);			
		pFn = (pFunction)temp;			
		pFn();			
	}										
}					

总结:
在这里插入图片描述

4.单继承无函数覆盖

#include <iostream>
using namespace std;
struct Base			
{			
public:			
    virtual void Function_1()			
    {			
        printf("Base:Function_1...\n");			
    }			
    virtual void Function_2()			
    {			
        printf("Base:Function_2...\n");			
    }			
    virtual void Function_3()			
    {			
        printf("Base:Function_3...\n");			
    }			
};			
struct Sub:Base			
{			
public:			
    virtual void Function_4()			
    {			
        printf("Sub:Function_4...\n");			
    }			
    virtual void Function_5()			
    {			
        printf("Sub:Function_5...\n");			
    }			
    virtual void Function_6()			
    {			
        printf("Sub:Function_6...\n");			
    }			
};			
			
							
void TestMethod()				
{				
	Sub sub;
	void (*pf)();
	for(int i=0;i<6;i++)
	{
		pf = (void (*)())(((int*)*((int*)&sub))[i]);
		pf();
	}
		
}	

int main()
{
	TestMethod();
}
	

在这里插入图片描述
说明6个函数在同一个表里,并且按照1,2,3,4,5,6排列,先父类再子类

5.单继承有函数覆盖(打印Sub对象的虚函数表)

#include <iostream>
using namespace std;
struct Base			
{			
public:			
    virtual void Function_1()			
    {			
        printf("Base:Function_1...\n");			
    }			
    virtual void Function_2()			
    {			
        printf("Base:Function_2...\n");			
    }			
    virtual void Function_3()			
    {			
        printf("Base:Function_3...\n");			
    }			
};			
struct Sub:Base			
{			
public:			
    virtual void Function_1()			
    {			
        printf("Sub:Function_1...\n");			
    }			
    virtual void Function_2()			
    {			
        printf("Sub:Function_2...\n");			
    }			
    virtual void Function_6()			
    {			
        printf("Sub:Function_6...\n");			
    }			
};			
	
							
void TestMethod()				
{				
	Sub sub;
	void (*pf)();
	for(int i=0;i<4;i++)
	{
		pf = (void (*)())(((int*)*((int*)&sub))[i]);
		pf();
	}
	printf("%d",sizeof(Sub));
}	

int main()
{
	TestMethod();
}

在这里插入图片描述

在这里插入图片描述
子类会覆盖父类的同名函数,且虚函数表地址在ebp-8,为啥不是ebp-4?理由未知
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值