12.3 虚基类
虚基类也被称为抽象类,既然是抽象事物,就不存在实体。如平常所说的东西,它就不能被实例化。将某一物品描述为东西,等同于没有描述。
在生活中,我们会经常遇到此类情况。例如,在你的书桌上有钢笔一支、水杯一个、书一本,这时你的同桌突然对你说:“把你桌子上的那个东西借我一下”,由于没有具体的描述,你无法知道他所指的“那个东西”到底是哪一件物品。
在编码过程中,虚基类的定义需要配合虚函数使用。在虚函数的声明结尾处添加“=0”,这种虚函数被称为纯虚函数。纯虚函数是一个没有实现只有声明的函数,它的存在就是为了让类具有虚基类的功能,让继承自虚基类的子类都具有虚表以及虚表指针。在使用过程中,利用虚基类指针可以更好地完成多态的工作。多态的实现分析已经在前面介绍过,而这个纯虚函数是如何实现的呢?对于一个没有实现的函数,编译器又是如何处理的呢?关于纯虚函数的分析如代码清单12-12所示。
代码清单12-12 纯虚函数的分析—Debug版
//C++源码说明:定义虚基类和派生类
class CVirtualBase{
public:
virtual void Show()=0;//定义纯虚函数
};
class CVirtualChild:public CVirtualBase{//定义继承虚基类的子类
public:
virtual void Show(){//实现纯虚函数
printf("虚基类分析\r\n");
}
};
void main(int argc, char*argv[]){
CVirtualChild VirtualChild;
VirtualChild.Show();
}
//反汇编代码分析,跟踪到虚基类构造函数中,查看其虚表信息
CVirtualBase:CVirtualBase://虚基类构造函数
00401829 pop
ecx
0040182A mov
dword ptr[ebp-4],ecx
0040182D mov
eax, dword ptr[ebp-4]
;设置虚基类虚表指针,虚表地址在0x00425068处,虚表信息如图12-10所示
00401830 mov
dword ptr[eax],offset CVirtualBase:'vftable'(00425068)
00401836 mov
eax, dword ptr[ebp-4]
0040183F ret
;虚基类CVirtualBase中虚表信息的第一项所指向的函数首地址
void__cdecl_purecall(void){
00401E90 push
ebp
00401E91 mov
ebp, esp
_amsg_exit(_RT_PUREVIRT);
00401E93 push
19h
;压入错误编码
00401E95 call
_amsg_exit(00401fd0)
;结束程序
00401E9A add esp,4
}
00401E9D pop ebp
00401E9E ret
图 12-10 虚基类CVirtualBase的虚表信息
如代码清单12-12所示,在虚基类CVirtualBase的虚表信息中,由于纯虚函数没有实现代码,因此没有首地址。编译器为了防止误调用纯虚函数,将虚表中保存的纯虚函数的首地址项替换成函数_purecall,用于结束程序,并发出错误编码信息0x19。
根据这一特性,在分析过程中,一旦在虚表中发现函数地址为_purecall函数的地址时,我们就可以高度怀疑此虚表对应的类是一个虚基类。当虚基类中定义了多个纯虚函数时,虚表中将保存相同的函数指针。在代码清单12-12中,插入新的纯虚函数,并在子类中予以实现。经过编译后,再次查看虚表信息,如图12-11所示。
图 12-11 存在多个纯虚函数的类虚表信息
在Release版下,编译器会进行优化,纯虚函数将会被优化掉。