11.3 本章小结

虚函数在面向对象领域中应用得十分广泛,可以说,没有虚函数也就没有多态性,更谈不上面向对象的软件设计了。虚函数在面向对象软件设计中无处不在。

通过本章的介绍可知,虚函数的调用不难识别,如下所示:


00401092 mov ecx, dword ptr[ebp-14h];ecx得到this指针

00401095 mov edx, dword ptr[ecx];edx得到虚表指针

;Debug选项组中有栈平衡检查,这里用esi保存栈顶,后面的__chkesp会用到

00401097 mov esi, esp

00401099 mov ecx, dword ptr[ebp-14h];成员函数调用,传递this指针

;关键,这里是个间接调用,且为成员函数,可怀疑是虚函数

0040109C call dword ptr[edx+4]

;以下是Debug选项组产生的栈平衡检查代码,与本章内容无关

;有兴趣的读者可以自己独立分析

0040109F cmp esi, esp

004010A1 call__chkesp(00401740)


如何确定[edx+4]一定是虚函数的地址呢?证实edx是虚函数表的首地址是关键,于是识别构造函数和析构函数尤为重要,IDA的引用参考能给我们很大帮助。我们先假设edx是虚表指针,然后去查询引用参考。如果假设成立,就能找到所有的构造函数和唯一的析构函数,再看构造函数和析构函数是否满足第10章中讨论的必要条件。充要条件都满足了,就可以将其认定为虚函数,同时找到了所有的构造函数和唯一的析构函数。反之,如果假设不成立,那就应该怀疑是开发者自定义的函数指针数组。

关于atexit的实现原理,请查阅VC安装目录下的\VC98\CRT\SRC\CRT0DAT.C文件,其中的_cinit函数里会调用_initterm函数。如果程序存在全局对象、静态对象或者有调用atexit函数,那么在执行_initterm函数中(**pfbegin)();的时候会执行_onexitinit函数,这个函数用于初始化终止函数数组,这个终止函数数组在堆内存中,由_onexit函数负责维护。在main函数退出后,调用exit函数,exit函数又会调用doexit。在doexit函数内,遍历终止函数数组,倒序调用。请读者阅读源码文件VC98\CRT\SRC\ONEXIT.C,或者单步跟入_cinit和atexit,对此代码进行分析印证。