虚函数
在名函数前加关键字“virtual”的函数为虚函数;
{
public:
void Function_1()
{
printf("Function_1...\n");
}
virtual void Function_2() //虚函数
{
printf("Function_2...\n");
}
};
反汇编代码
总结
通过对象调用时,virtual函数与普通函数都是E8 Call
通过指针调用时,virtual函数是FF Call,也就是间接Call
IAT表使用的就是FF Call ;
使用间接call的原因是因为虚函数的地址不靠谱,可能被改掉;
比如,子类覆盖父类函数时;
纯虚函数
C++中的纯虚函数是没有函数体的虚函数,也称为“纯虚函数接口”。纯虚函数在基类中用于定义一个接口,其具体实现则由派生类完成。
纯虚函数使用纯虚函数语法 virtual returnType functionName() = 0;
声明,其中 =0
表示这个函数为纯虚函数,没有实现。
例如,下面是一个名为 Shape
的基类,其中包含一个纯虚函数 getArea()
:
class Shape {
public:
virtual double getArea() = 0;
virtual ~Shape() {} // 确保析构函数为虚函数
};
派生类必须重写基类中的纯虚函数,否则它们也会成为抽象类。以下示例是一个名为 Rectangle
的派生类,它重写了 Shape
类中的 getArea()
函数:
class Rectangle : public Shape {
public:
double getArea() { return width * height; }
void setWidth(double w) { width = w; }
void setHeight(double h) { height = h; }
private:
double width;
double height;
};
请注意,在纯虚函数声明后,必须提供括号中的 =0
。纯虚函数不能包含函数体,否则编译器将抛出错误。
深入虚函数调用
#include "stdafx.h"
class Base
{
public:
int x;
int y;
Base(){
x=1;
y=2;
}
void fun(){
}
virtual fun2(){
}
};
int main(int argc, char* argv[])
{
Base b;
Base* pb = &b;
pb ->fun2();
getchar();
return 0;
}
上面结构的大小为 0x0C
普通函数放结构体里面时,不会影响结构体大小,结构大小应该是两个int的大小8个字节;
但在结构体中有一个或多个虚函数时,多了4个字节;
这四个字节其实是一个地址,指向虚函数表
ecx里存的是this指针,this指针指向结构的首地址,而多出的四字节就在结构的首地址处,所以this指针指向的地方里面存了四字节,这四字节指向了虚表的地址,
将42501c存入edx,然后FF call这个地址;
如果有多个虚函数反汇编:
可以看到,第一个虚函数call的是[edx],第二个虚函数call的是[edx+4];
总结:
1、当类中有虚函数时,会多一个属性,4个字节
2、多出的属性是一个地址,指向一张表,里面存储了所有虚函数的地址
解析虚函数表
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();
}
}
练习
函数1(单继承无函数覆盖(打印Sub对象的虚函数表)):
#include"c++test.h"
class 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");
}
};
class 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 test()
{
Sub base;
printf("虚函数表的地址:%X\n", *(int*)&base);
typedef void(*pFunction)(void);
pFunction pfn;
for (int i = 0; i < 6; i++)
{
int Temp = *((int*)(*(int*)&base) + i);
pfn = (pFunction)Temp;
pfn();
}
}
int main(int argc, char* argv[])
{
test();
}
继承时先复制了父类的信息,所以虚函数表先存储父类的虚函数
函数2(单继承有函数覆盖(打印Sub对象的虚函数表)):
#include"c++test.h"
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 test()
{
Sub base;
printf("虚函数表的地址:%X\n", *(int*)&base);
typedef void(*pFunction)(void);
pFunction pfn;
for (int i = 0; i < 6; i++)
{
int Temp = *((int*)(*(int*)&base) + i);
pfn = (pFunction)Temp;
pfn();
}
}
int main(int argc, char* argv[])
{
test();
}
子类覆盖了父类的虚函数地址,所以前两个函数变成子类重写后的函数地址