内存泄漏问题及其解决方案

在软件开发中,内存泄漏是一个常见的问题,尤其是在处理数据库连接和操作时。本文将探讨一个在特定情况下发生的内存泄漏问题,这个问题虽然在大多数情况下不会立即显现,但如果服务器代码24小时不间断运行,最终会导致内存耗尽。这个问题与之前讨论的CCommand内存泄漏问题有所不同,其泄漏的内存量较小,但同样不容忽视。

背景信息:微软知识库描述了一个众所周知的内存泄漏问题及其解决方法。然而,还有一个微妙的内存泄漏问题尚未被广泛报道。很可能没有人像项目中那样使用ATL OLEDB。在项目中,性能至关重要,因此通常会准备带有参数的命令,并使用不同的参数运行它。使用它在一天中实时地向SQL Server写入交易订单和其他内容。这样,可以获得最佳性能,因为不需要一次又一次地生成SQL字符串并准备它。

谁会像这个例子中那样运行同一个命令一百万次?会。想法是准备带有参数的命令,并用不同的参数运行它。使用它在一天中实时地向SQL Server写入交易订单和其他内容。这样,可以获得最佳性能,因为不需要一次又一次地生成SQL字符串并准备它。

以下是使用C++和ATL OLEDB实现的代码示例: CDataSource ds; //Create and open the data source CDBPropSet dbinit(DBPROPSET_DBINIT); dbinit.AddProperty(DBPROP_INIT_DATASOURCE, "192.168.60.18"); dbinit.AddProperty(DBPROP_AUTH_USERID, "user"); dbinit.AddProperty(DBPROP_AUTH_PASSWORD, ""); dbinit.AddProperty(DBPROP_INIT_CATALOG, "master"); dbinit.AddProperty(DBPROP_AUTH_PERSIST_SENSITIVE_AUTHINFO, false); dbinit.AddProperty(DBPROP_INIT_LCID, 1033L); dbinit.AddProperty(DBPROP_INIT_PROMPT, static_cast(4)); HRESULT hr = ds.Open(_T("SQLOLEDB.1"), &dbinit); //Create Session. CSession session; session.Open(ds); CCommand command; hr = command.Create(session, "exec sp_tables"); for (int i = 0; i < 1000000; i++) { hr = command.Open(NULL,NULL, false, 0); command.Close(); if (command.GetMultiplePtr() != NULL) { command.GetMultiplePtr()->Release(); *command.GetMultiplePtrAddress() = NULL; } } 如果将这段代码复制到项目中并运行它(请使用正确的用户和密码为数据库环境),会发现任务管理器中的内存使用量以每秒10K的速度增加,尽管在另一篇文章中提到的主要内存泄漏已经在这里修复了。

这个问题的关键在于使用CDynamicParameterAccessor而不是CDynamicAccessor。当没有参数时,这个小内存泄漏就发生在CDynamicParameterAccessor上。显然,如果非常小心地在不需要参数时使用CDynamicAccessor,在存储过程有参数时使用CDynamicParameterAccessor,可以绕过这个bug。然而,在某些情况下,不想区分它们,因为可能会将命令包装到一个新的类中,并希望无论存储过程是否有参数都能使用它。

问题的核心在于CCommand::Open()调用了CDynamicParameterAccessor::BindParameters()。以下是CDynamicParameterAccessor::BindParameters()的实现: HRESULT BindParameters(HACCESSOR* pHAccessor, ICommand* pCommand, void** ppParameterBuffer, bool fBindLength = false, bool fBindStatus = false) throw() { // If we have already bound the parameters then just return the pointer to the parameter buffer if (*pHAccessor != NULL) { *ppParameterBuffer = m_pParameterBuffer; return S_OK; } ... } 这段代码检查*pHAccessor是否为NULL。如果不是NULL,意味着参数已经绑定,没有必要分配与参数相关的内存,也没有必要绑定参数访问器。这通常没问题。唯一的问题是,当存储过程没有参数时,BindEntries()会失败。在这种情况下,*pHAccessor仍然是NULL,但所有与参数相关的内存都已分配。是的,因为ulParams0,因为存储过程不需要参数。然而,AllocateParameterInfo()会分配DBBINDING[0]OLECHAR*[0]。这些操作实际上分配了一个好指针,内容长度为0。在发布模式下,两者都消耗了16字节的内存。在调试版本中为64字节。由于每次*pHAccessorNULL时,都会分配这块小内存。只有在析构函数中最后一次分配的内存才会被真正释放: ~CDynamicParameterAccessor() { delete[] m_pParameterEntry; if (m_ppParamName != NULL) { CoTaskMemFree(*m_ppParamName); delete[] m_ppParamName; } delete m_pParameterBuffer; }

沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485