临时对象引起的内存泄漏
※ 问题
程序退出时,VC 开发环境提示有多处内存泄漏。
※ 调试
经过数次尝试之后,找到了重现的方法,以及问题出现的操作路径。
于是,使用 _CrtSetBreakAlloc()
来进一步地分析。这是一个 Win32 API,它的用处是在堆上第 { 指定 } 次内存分配时,产生一次异常,调试程序从而可以接管整个程序,直接定位到是哪里的内存分配。这个 API 的调用位置,通常是在程序初始化的时候。
添加调试代码、编译、运行,调试器很顺利地捕获到了这个断点,停留在了
{
obj.SetArgs(&ArgsWrapper(&info));
this->Do(&obj);
}
这段代码里,先构造一个 ArgsWrapper 的 C++ 对象,然后它又作为参数,传递给 SetArgs 函数。后续地,会调用 Do 函数来执行。
ArgsWrapper 是一个 C++ 的类,它内部里有多个子类对象,都会有堆内存的申请操作,在这些子类对象的析构函数里会进行释放。既然调试器提示了,就说明这些析构函数可能没有调用到,更进一步地,作为父类的 ArgsWrapper 的析构函数没有调用到。
在 ArgsWrapper 的构造函数及析构函数里添加了打印,再次运行后,发现构造函数和析构函数都有调用到,但在 obj.SetArgs 调用时即已经完成。这说明,析构在接口方法使用前已经被调用,之后调用的接口方法里申请到的内存,已经没有了释放的时机。看来是由于没有控制好这个临时 C++ 对象的生命周期,从而引起了内存泄漏的问题。
那另外一个问题是,为什么析构以后这个对象还是可以访问呢。这个问题应该是这样的,上述代码里定义了一个临时变量,它本身的位置在当前函数的调用栈上,这片内存会在这个函数调用结束后才会被释放,所以在此期间,虽然已经被构了,但内存还是有效的,可以正常操作,除了一点,那就是它的析构函数不会被再次调用。
※ 解决
找到了问题的原因,同时也定位了问题代码的位置,修改就容易了。将上述代码拆分成两句,即先定义一个变量,然后将变量传递给后续函数使用:
{
ArgsWrapper wrapper(&info);
obj.SetArgs(&wrapper);
this->Do(&obj);
}
这样就可以避免临时对象生命周期短的问题了。