【问题标题】:Opening database in invalid location causes memory leak在无效位置打开数据库会导致内存泄漏
【发布时间】:2011-05-18 09:51:14
【问题描述】:

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

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

【问题讨论】:

  • @DumbCode 我没有尝试优化任何东西,但我正在尝试对我的代码进行单元测试。问题是在打开数据库出现问题时如何测试案例。正如您在示例中看到的,我将“/”(根目录)作为数据库名称传递以模拟这种情况。

标签: c++ linux qt sqlite valgrind


【解决方案1】:

你的代码

realdb.reset( new QSqlDatabase( QSqlDatabase::addDatabase( "QSQLITE", "ConnName" ) ) );

看起来很奇怪,不知道为什么会编译。我没有看到将 QSqlDatabase* 作为参数的 QSqlDatabase 构造函数。

您调用了返回 QSqlDatabase * 的 QSqlDatabase::addDatabase 然后使用 new 构造另一个 QSqlDatabase 并将其作为参数传递。

你可以使用 boost::shared_ptr 来代替 auto_ptr 然后重置

realdb.reset(QSqlDatabase::addDatabase( "QSQLITE", "ConnName" ), QSqlDatabase::removeDatabase);

请注意,如果存在打开的查询,removeDatabase 可能会导致资源泄漏。

【讨论】:

  • 再看看。 auto_ptr 的构造函数需要指针。在 open() 方法中,我正在打开连接并尝试打开数据库。在析构函数中,我正在破坏 QSqlDatabase 对象,然后删除连接,因此没有资源泄漏。
  • 我忘了提到这个类不应该是可复制的(它继承自 boost::noncopyable),因此在这种情况下使用 shared_ptr 与使用 auto_ptr 相同。
  • 您在构造对象的 QSqlDatabase 上调用 new 。该接口虽然没有为 QSqlDatabase 提供公共构造函数,但需要调用 QSqlDatabase::addDatabase 来构造。因此,除非我查看错误的文档,否则我对您的代码编译感到惊讶。您不能只将 QSqlDatabase::addDatabase 的结果传递给 auto_ptr,因为您应该使用 removeDatabase 来关闭它,而不是直接删除
  • 不,QSqlDatabase::addDatabase 正在构造对象,我在堆上复制构造它并放置指向 auto_ptr 的指针。在调用 QSqlDatabase::removeDatabase 之前,我正在破坏对象,以避免资源泄漏。
【解决方案2】:

浏览了 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() 应该被称为任何一种情况。

另一方面,http://www.sqlite.org/c3ref/close.html 声称如果 NULL 被传递为句柄(如果打开失败,则为空操作)。查看 SQLite 源代码(DIY - 我不知道它的 Web 源浏览器界面)确认,如果使用 NULL 调用它只会返回。

好吧 - 现在是这个问题的本质乐趣......

天真地,假设sqlite3_open*() 的失败将暗示NULL db 句柄。但根据 SQLite 的消息来源,在 main.c 中阅读 openDatabase(),这不是真的 - 调用可能会失败,但仍会返回非 NULL 的数据库句柄。

Qt 似乎假设打开数据库连接失败意味着接收到NULL 数据库句柄。但这不是 SQLite 所做的。不过,文档可能会更清晰。

尝试将其添加到QSQLiteDriver::open() 中,看看它是否修复了泄漏。如果是这样,请向 Qt 人员提交一个错误,然后向 SQLite 人员提交另一个错误以澄清文档;-)

【讨论】:

  • 更好地创建一个 RAII 对象来为你做这件事。我不一定希望它不会泄漏,但我希望 Qt 提供能够正确清理资源的自动对象。
  • @CashCow:无论如何,如果 SQLite 需要关闭所有非 NULL 句柄,那么如果它不这样做,那就是 Qt 中的一个错误。没什么大不了;错误有待修复。
  • @CashCow:QSQL 设计使用 RAII,但解决问题要求实现和设计意图匹配。
猜你喜欢
  • 1970-01-01
  • 2011-09-27
  • 1970-01-01
  • 1970-01-01
  • 2016-03-14
  • 1970-01-01
  • 2021-03-23
  • 2021-09-25
  • 2014-12-08
相关资源
最近更新 更多