在无效位置打开数据库会导致内存泄漏

时间:2010-12-01 09:57:41

标签: c++ linux qt sqlite valgrind

我使用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>

有谁知道如何解决这个漏洞?

2 个答案:

答案 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可能会导致资源泄漏。