现象、问题描述
一个话统工具的调试过程中,出现访问非法内存地址。在执行一条数据库操作语句时候出现非法内存地址访问,单步多次跟踪和调试发现是一个全局变量冲突导致非法访问,同时定位一个内存泄漏问题,本案例就发现的内存泄漏问题进行分析。
关键过程、根本原因分析
在调用一个函数按照给定的条件查询数据库,并将查询的结果返回时,出现非法访问操作,执行的函数调用原型是HRESULT CNastarDB::ExecQueryManual(const CString &strQuery, _DBResult* pRes),第一个参数是查询条件,第二个参数是一个类指针,类_DBResult有两个指针成员,一个模板类变量。
设计者的数据库查询设计思想是:在使用ExecQueryManual进行查询前,先定义一个_DBResult变量,将该变量的地址作为参数传给查询函数,函数ExecQueryManual查询时候,从堆中分配内存给_DBResult的一个成员变量m_pDynColumn,然后将查询结果设置后返回,在类_DBResult的析构函数中,将从堆中分配的内存释放。代码例示为:
_DBResult* pRes = new _DBResult CRes;
//初始化pRes;
……
ExecQueryManual(strQuery, pRes); //查询调用
……
CRes生命结束,析构函数此时执行(释放m_pDynColumn指向的内存);
其中,查询调用函数:
ExecQueryManual(const CString &strQuery, _DBResult* pRes)
{
……
pRes->m_pDynColumn = new _DYNBIND_COLUMN[ulColumns];
……
}
在一般的情况下,这样使用不会导致内存泄漏,但代码中发现了这样的调用:
……
_DBResult* pRes = new _DBResult CRes;
//初始化pRes;
……
for(;;;) //for的条件未给出
{
……
ExecQueryManual(strQuery, pRes); //查询调用
……
}
CRes生命结束,析构函数此时执行(释放m_pDynColumn指向的内存);
这样的情况下,只有最后一次分配的内存被释放了,前面的内存出现泄漏。
结论、解决方案及效果
对于此内存泄漏问题,有两种解决方案:
1、将_DBResult* pRes = new _DBResult CRes;放在for循环内;
2、在函数ExecQueryManual内分配内存前,先判断是否为空,如果不为空,先释放。
方法2比方法1要好些,因为方法2在ExecQueryManual的使用者层面上是安全的(既不会出现内存泄漏)
按照方法2修改,此问题得到解决。
经验总结、预防措施和规范建议
1)上述两种方法都不是最好的解决方法,理由是该两个类的耦合性太强,必须要知道其内部实现细节,才能安全使用,这不符合封装的原则。
2)接口函数内存分配,释放原则:
设计函数调用接口时应尽量避免被调函数给out/inout参数分配内存,调用函数释放内存
如果不能避免被调函数给out/inout参数分配内存,在函数说明中需要显式说明,并且在分配内存前,需要对out/inout参数进行检测,确保内存释放,防止内存泄漏;调用函数释放内存的操作符应该和分配内存操作符一致,如new/delete;malloc/free等
备注
无。
考核点
接口函数内存分配,释放原则
试题
如下说法,哪些是正确的(A B C D)
A: 设计函数调用接口时应尽量避免被调函数给out/inout参数分配内存,调用函数释放内存
B:如果不能避免被调函数给out/inout参数分配内存,在函数说明中需要显式说明,并且在分配内存前,需要对out/inout参数进行检测,采取措施,避免内存泄漏
C: 如果不能避免被调函数给out/inout参数分配内存,调用函数释放内存的操作符应该和分配内存操作符一致,如new/delete;malloc/free等
D:调用给out/inout参数分配内存的函数,一定要确保,处理结束后必须释放作为in/out参数由被调函数分配的内存
阅读(426) | 评论(0) | 转发(0) |