使用 IDA 打开 对应的程序,找到 GetParam() 的反汇编,可以发现一个有意思的事情是 GetParam() 的形式。本来声明的是
ConfigParam GetParam(int option),在 IDA 中看到的却是 ConfigParam *__fastcall GetParam(ConfigParam *result, int option)。如下图:
依稀记得多年前接触汇编的时候,了解到一种说法:如果返回值类型比较大(大家应该知道在 32 位程序中,函数的返回值基本是通过 EAX 反回的),那么会把返回值的地址当作第一个参数传递给函数,EAX 指向的是返回值的地址。正好跟 IDA 对应上了。
代码中的 GetParam() 函数,当 option 是 0 的时候,会反回局部的 result,否则什么都不做。看看编译器帮我们做了什么吧。编译器做的事情也是,当 option 为 0 的时候,执行拷贝构造函数把局部的 result 返回出去,否则不会对参数中的 result 做任何操作。关键代码如下图所示:
那么在调用 GetParam() 函数的地方,会对 result 做什么初始化的工作吗?
从下图可以清楚的看到,main() 函数并没有对 result 做任何初始化就传递给了 GetParam() 函数。
所以,调用完 GetParam() 后,main() 函数中的 result 是一个未初始化的对象。而不是一个调用过构造函数的对象。所以后面再调用其析构函数的时候,发生什么事情都是正常的了。我在遇到这个问题之前,一直以为 GetParam() 函数返回来的是一个初始化过的对象,因为根据之前的认知,在对象产生的时候一定会调用构造函数。这里既没有调用构造函数,也没有调用拷贝构造函数。
这个问题最先是在 vs2019 上发现的,我还以为是 vs2019 的 bug,于是试了 vs2017、vs2013、vs2010,发现都会崩溃。但是每个版本的 vs 都会给出一个警告:warning C4715: 'GetParam' : not all control paths return a value。
虽然给了警告,但是多少还是觉得 vs 的处理不太合理,难道所有编译器都是这个行为吗?试试 gcc 中的行为。