将 sqlite3 与 C++ 一起使用时出现内存泄漏

Memory leak when using sqlite3 with C++

本文关键字:内存 泄漏 sqlite3 C++ 一起      更新时间:2023-10-16

程序写入SQLite数据库,消息通过无线模块接收。但是不知何故,每次接收消息并将其写入数据库时都会发生内存泄漏,在大约 10 000 次写入后,程序正在使用 1GB 的内存。

带有C++的 SQLite3 的文档说,内存泄漏可以通过存在的sqlite3_finalize()sqlite3_close()来防止:

#include <iostream>
#include <string>
#include <sstream>
#include "sqlite3.h"
using namespace std;
#define DB "test.db"
sqlite3 *dbfile;
bool connectDB();
void disonnectDB();
int insOrUpdate(string s);
int select(string s);
struct messageStruct_t {
  float value;
};
bool isOpenDB = false;
int main() {
  int counter = 0;
  while (1) {
    int header = 1;
    int message = rand() % 3;
    if (message) {
      counter ++;
      switch (header) {
      case 1: {
        messageStruct_t recMessage;
        recMessage.value = 55;
        int receivedSendersID = 2;
        //SQL query to get foreign key
        stringstream strm_select;
        strm_select << "SELECT id FROM table1 WHERE sendersID="
                    << receivedSendersID;
        string s_select = strm_select.str();
        cout << "SQL query: " << s_select << endl;
        int sendersID = select(s_select);
        cout << "Sender's ID: " << sendersID << endl;

        if (sendersID == 0) {
          cout << "Error: Sender doesn't existn";
        } else {
          stringstream strm_insert;
          strm_insert << "INSERT into table2(id,value,sender_id) values("
                      << counter << ", "
                      << recMessage.value << ", " << sendersID << ")";
          string s_insert = strm_insert.str();
          cout << "SQL query: " << s_insert << endl;
          insOrUpdate(s_insert);
          cout << "Recorded data: " << recMessage.value << endl;
        }
      }
      default: {
        break;
      }
      }
    }
  }
}
bool connectDB () {
  if (sqlite3_open(DB, &dbfile) == SQLITE_OK) {
    isOpenDB = true;
    return true;
  }
  return false;
}
void disonnectDB () {
  if ( isOpenDB == true ) {
    sqlite3_close(dbfile);
  }
}
int insOrUpdate(string s) {
  if (!connectDB()) {
    return 0;
  }
  char *str = &s[0];
  sqlite3_stmt *statement;
  int result;
  const char *query = str;
  if (sqlite3_prepare(dbfile, query, -1, &statement, 0) == SQLITE_OK) {
    result = sqlite3_step(statement);
    //the documentation says that this destroys the statement and prevents memory leaks
    sqlite3_finalize(statement);
    return result;
  }
  //and this destroys the db object and prevents memory leaks
  disonnectDB();
  return 0;
}
int select(string s) {
  if (!connectDB()) {
    return 0;
  }
  char *str = &s[0];
  sqlite3_stmt *statement;
  const char *query = str;
  string returned;
  if (sqlite3_prepare(dbfile, query, -1, &statement, 0) == SQLITE_OK) {
    int ctotal = sqlite3_column_count(statement);
    int res = 0;
    while (1) {
      res = sqlite3_step(statement);
      if (res == SQLITE_ROW) {
        for (int i = 0; i < ctotal; i++) {
          string s = (char*)sqlite3_column_text(statement, i);
          cout << s << " ";
          returned = s;
        }
        cout << endl;
      }
      if (res == SQLITE_DONE || res == SQLITE_ERROR) {
        cout << "done " << endl;
        break;
      }
    }
  } else {
    cout << "Can't prepare" << endl;
    return 0;
  }
  sqlite3_finalize(statement);
  disonnectDB();
  int result;
  stringstream convert(returned);
  if (!(convert >> result)) {
    result = 0;
  }
  return result;
}

CREATE TABLE table1 (
id INTEGER NOT NULL,
sendersID INTEGER,
PRIMARY KEY (id)
);
CREATE TABLE table2 (
id INTEGER NOT NULL,
value FLOAT,
sender_id INTEGER,
FOREIGN KEY(sender_id) REFERENCES table1 (id)
);
INSERT INTO table1(sendersID) values(2);

在 connectDB(..) 调用中,在再次打开数据库之前,您不会检查数据库是否已打开。内存泄漏可能是由于此数据库重复映射到内存空间造成的。

该程序可能存在其他问题,但下面对connectDB(..)的更改应该有助于解决每次成功插入时的泄漏。

bool connectDB() {
    if (false == isOpenDB && sqlite3_open(DB, &dbfile) == SQLITE_OK) {
        isOpenDB = true;
    }
    return isOpenDB;
}

您绝对应该将 RAII 用于您的连接和语句。有几个地方你提前返回,没有清理语句和/或关闭连接。