我使用qt 4.5.3访问sqlite数据库,如下所示:
class db : private boost::noncopyable
{
public:
db( QString file ) : filename( file ),
realdb( NULL ),
theConnectionEstablished( false )
{
}
~db()
{
if ( NULL != realdb.get() )
{
realdb.reset( NULL );
}
if ( theConnectionEstablished )
{
QSqlDatabase::removeDatabase( "ConnName" );
}
}
void open()
{
realdb.reset( new QSqlDatabase( QSqlDatabase::addDatabase( "QSQLITE", "ConnName" ) ) );
theConnectionEstablished = true;
// open the db
realdb->setDatabaseName( filename );
if ( ! realdb->open() )
{
const QSqlError dbError = realdb->lastError();
const QString errorDesc = "Error opening the database : " + filename +
"\nDatabase error : " + dbError.databaseText() +
"\nDatabase driver error : " + dbError.driverText();
// DatabaseError is a class type which accepts the std::string for logging purposes
throw DatabaseError( errorDesc.toStdString() );
}
}
const QString filename;
std::auto_ptr<QSqlDatabase> realdb;
bool theConnectionEstablished;
};
现在如果我尝试像这样测试这个案例(我使用cxxtest):
void test_failed_connection()
{
db obj( "/" );
TS_ASSERT_THROWS( obj.open(), DatabaseError );
}
我收到valgrind报告的内存泄漏:
<error>
<unique>0x5b</unique>
<tid>1</tid>
<kind>Leak_DefinitelyLost</kind>
<what>986 (384 direct, 602 indirect) bytes in 1 blocks are definitely lost in loss record 23 of 23</what>
<leakedbytes>986</leakedbytes>
<leakedblocks>1</leakedblocks>
<stack>
<frame>
<ip>0x4006D3E</ip>
<obj>/opt/valgrind341/lib/valgrind/x86-linux/vgpreload_memcheck.so</obj>
<fn>malloc</fn>
<dir>/home/slawomir/valgrind-3.4.1/build/valgrind-3.4.1/coregrind/m_replacemalloc</dir>
<file>vg_replace_malloc.c</file>
<line>207</line>
</frame>
<frame>
<ip>0x67FADC4</ip>
<obj>/usr/lib/libsqlite3.so.0.8.6</obj>
<fn>sqlite3_malloc</fn>
</frame>
<frame>
<ip>0x67FAF13</ip>
<obj>/usr/lib/libsqlite3.so.0.8.6</obj>
</frame>
<frame>
<ip>0x6816DA3</ip>
<obj>/usr/lib/libsqlite3.so.0.8.6</obj>
</frame>
<frame>
<ip>0x68175FD</ip>
<obj>/usr/lib/libsqlite3.so.0.8.6</obj>
<fn>sqlite3_open16</fn>
</frame>
<frame>
<ip>0x40DDEF9</ip>
<obj>/usr/lib/qt4/plugins/sqldrivers/libqsqlite.so</obj>
</frame>
<frame>
<ip>0x7F34AE0</ip>
<obj>/usr/lib/libQtSql.so.4.5.2</obj>
<fn>QSqlDatabase::open()</fn>
</frame>
</frame>
</stack>
</error>
有谁知道如何解决这个漏洞?
答案 0 :(得分:3)
浏览Qt和sqlite来源......很有趣。
阅读sqlite3_open16()
,http://www.sqlite.org/c3ref/open.html的手册,可以找到以下引用:
打开时是否发生错误,应该通过在不再需要时将其传递给sqlite3_close()来释放与数据库连接句柄相关联的资源。
QSQLiteDriver::close()
似乎只是在公开成功的情况下调用http://qt.gitorious.org/qt/qt/blobs/4.7/src/sql/drivers/sqlite/qsql_sqlite.cpp。 SQLite的文档可能表明sqlite3_close()
应该被称为case。
另一方面,http://www.sqlite.org/c3ref/close.html声称如果为句柄传递NULL
则为无操作(如果打开失败则为{})。查看SQLite源代码(DIY - 我不知道它的Web源浏览器界面)确认,如果使用NULL
调用它,它就会返回。
嗯 - 现在为了这个问题的细节之处......
天真地,假设sqlite3_open*()
的失败意味着NULL
db句柄。但根据SQLite的消息来源,在openDatabase()
中读取main.c
,这不是真的 - 调用可能会失败,但仍会返回非NULL
数据库句柄。
Qt看起来假设无法打开数据库连接意味着接收NULL
数据库句柄。但这不是SQLite所做的。但是文档可能更清晰。
尝试将其添加到QSQLiteDriver::open()
并查看是否修复了泄漏。如果是这样,请向Qt人员提交一个错误,另一个与SQLite人员提交文件澄清; - )
答案 1 :(得分:0)
您的代码
realdb.reset( new QSqlDatabase( QSqlDatabase::addDatabase( "QSQLITE", "ConnName" ) ) );
看起来很奇怪,不知道为什么要编译。我没有看到QSqlDatabase的构造函数将QSqlDatabase *作为参数。
您调用了QSqlDatabase :: addDatabase,它返回QSqlDatabase *,然后使用new构建另一个QSqlDatabase并将其作为参数传递。
您可以使用boost :: shared_ptr而不是auto_ptr,然后使用
重置 realdb.reset(QSqlDatabase::addDatabase( "QSQLITE", "ConnName" ), QSqlDatabase::removeDatabase);
请注意,如果存在打开的查询,removeDatabase可能会导致资源泄漏。