10.4 本章小结

搞清楚了构造函数与析构函数的出现时机,就掌握了识别它们的基础知识。就像警察抓罪犯,要抓住罪犯首先必须掌握罪犯的行踪,搞清楚罪犯经常出没的地点,才能在这些地点设立关卡。根据情报得知,罪犯会在不同的地点穿着对应的服装以掩饰身份。有了这些情报,只需在这些地点进行排查即可。如果发现可疑人物,且与情报描述的特征极为相似时,便可对犯罪嫌疑人实施抓捕。

构造函数与析构函数的识别过程也是如此,它们的出没地点和特征已经被我们掌握,剩下的就是在这些地点设立关卡,等待它们的到来。

得知了构造函数和析构函数调用所经过的路线,只需要在这些地方严密监控,根据构造函数与析构函数的特征,排查可疑数据,便可找到构造函数和析构函数。

构造函数的必要条件:

这个函数的调用,是这个对象在作用域内的第一次成员函数调用,看this指针即可以区分对象,是哪个对象的this指针就是哪个对象的成员函数;

使用thiscall调用方式,使用ecx传递this指针;

返回值为this指针。

析构函数的必要条件:

这个函数的调用,是这个对象在作用域内的最后一次成员函数调用,看this指针即可以区分对象,是哪个对象的this指针就是哪个对象的成员函数;

使用thiscall调用方式,使用ecx传递this指针;

没有返回值。

以上是本书总结出来的识别构造函数和析构函数的必要条件。构造函数和析构函数必须分别满足对应的3个条件。但是,就算满足这3个条件,还不能充分断定构造函数或析构函数。识别构造函数和析构函数的充分条件是有虚表指针初始化的操作和写入虚表指针的操作,这一点在后面的章节中会继续讨论。

使用O2选项优化后的构造函数与析构函数的调用与在Debug版中的调用相似,其实现流程大致相同。读者可参考10.1节与10.3节中对构造函数与析构函数的讲解,按照流程对照分析。在本书随书文件中为读者准备了简单的示例分析程序,见第10章的工程ShowNumber,该工程内有一个字符串处理类,读者可分析此工程生成的Release版程序,还原出其等价的高级代码,并修复程序中遗留下的bug,以增强对构造函数和析构函数的认识。

值得一提的是,在Debug选项组编译的时候,默认使用了/Ob0选项,这个选项关闭了内联函数(inline关键字),但是在使用Release选项组时候,不但开放了内联函数,而且尝试设置每个函数都是内联函数,对构造函数和析构函数也是如此。对于这种情况,我们只能分析并还原出功能等价的代码,然后根据程序逻辑判断构造函数和析构函数。

思考题答案:

编译器的创建者在完成用于在main函数前执行初始化操作的_initterm函数时,将自己所做的各类初始化函数的指针统一定义为如下形式:


typedef void(__cdecl*_PVFV)(void);


然而,由于构造函数可以重载,因此其参数的类型、个数和顺序都无法预知,也就无法预先定义构造函数。函数参数如何匹配呢?如何保证栈顶平衡呢?最简洁的办法就是使用代理函数。

编译器为每个全局对象分别生成构造代理函数,由代理函数去调用各类形形色色的参数和约定的构造函数。由于代理函数的类型被统一指定为PVFV,因此能通过数组统一地管理和执行。